diff --git a/Makefile b/Makefile index 9ed40e644b9..7de55571c0e 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ LIBSECCOMP_COMMIT := release-2.3 EXTRA_LDFLAGS ?= BUILDAH_LDFLAGS := $(GO_LDFLAGS) '-X main.GitCommit=$(GIT_COMMIT) -X main.buildInfo=$(SOURCE_DATE_EPOCH) -X main.cniVersion=$(CNI_COMMIT) $(EXTRA_LDFLAGS)' -SOURCES=*.go imagebuildah/*.go bind/*.go chroot/*.go copier/*.go define/*.go docker/*.go internal/parse/*.go internal/source/*.go internal/util/*.go manifests/*.go pkg/chrootuser/*.go pkg/cli/*.go pkg/completion/*.go pkg/formats/*.go pkg/overlay/*.go pkg/parse/*.go pkg/rusage/*.go pkg/sshagent/*.go pkg/umask/*.go pkg/util/*.go util/*.go +SOURCES=*.go imagebuildah/*.go bind/*.go chroot/*.go copier/*.go define/*.go docker/*.go internal/mkcw/*.go internal/mkcw/types/*.go internal/parse/*.go internal/source/*.go internal/util/*.go manifests/*.go pkg/chrootuser/*.go pkg/cli/*.go pkg/completion/*.go pkg/formats/*.go pkg/overlay/*.go pkg/parse/*.go pkg/rusage/*.go pkg/sshagent/*.go pkg/umask/*.go pkg/util/*.go util/*.go LINTFLAGS ?= @@ -69,9 +69,22 @@ static: mkdir -p ./bin cp -rfp ./result/bin/* ./bin/ -bin/buildah: $(SOURCES) cmd/buildah/*.go +bin/buildah: $(SOURCES) cmd/buildah/*.go internal/mkcw/embed/entrypoint.gz $(GO_BUILD) $(BUILDAH_LDFLAGS) $(GO_GCFLAGS) "$(GOGCFLAGS)" -o $@ $(BUILDFLAGS) ./cmd/buildah +ifneq ($(shell as --version | grep x86_64),) +internal/mkcw/embed/entrypoint: internal/mkcw/embed/entrypoint.s + $(AS) -o $(patsubst %.s,%.o,$^) $^ + $(LD) -o $@ $(patsubst %.s,%.o,$^) + strip $@ +else +.PHONY: internal/mkcw/embed/entrypoint +endif + +internal/mkcw/embed/entrypoint.gz: internal/mkcw/embed/entrypoint + $(RM) $@ + gzip -k $^ + .PHONY: buildah buildah: bin/buildah diff --git a/buildah.go b/buildah.go index c1259b92597..ac4ed119735 100644 --- a/buildah.go +++ b/buildah.go @@ -386,6 +386,11 @@ type ImportFromImageOptions struct { SystemContext *types.SystemContext } +// ConfidentialWorkloadOptions encapsulates options which control whether or not +// we output an image whose rootfs contains a LUKS-compatibly-encrypted disk image +// instead of the usual rootfs contents. +type ConfidentialWorkloadOptions = define.ConfidentialWorkloadOptions + // NewBuilder creates a new build container. func NewBuilder(ctx context.Context, store storage.Store, options BuilderOptions) (*Builder, error) { if options.CommonBuildOpts == nil { diff --git a/buildah_test.go b/buildah_test.go index c3d39242769..cb9284dfbb5 100644 --- a/buildah_test.go +++ b/buildah_test.go @@ -14,16 +14,22 @@ import ( ) func TestMain(m *testing.M) { + var logLevel string debug := false if InitReexec() { return } flag.BoolVar(&debug, "debug", false, "turn on debug logging") + flag.StringVar(&logLevel, "log-level", "error", "log level") flag.Parse() - logrus.SetLevel(logrus.ErrorLevel) - if debug { - logrus.SetLevel(logrus.DebugLevel) + level, err := logrus.ParseLevel(logLevel) + if err != nil { + logrus.Fatalf("error parsing log level %q: %v", logLevel, err) } + if debug && level < logrus.DebugLevel { + level = logrus.DebugLevel + } + logrus.SetLevel(level) os.Exit(m.Run()) } diff --git a/cmd/buildah/commit.go b/cmd/buildah/commit.go index 02f6ec13afa..37ce52b84aa 100644 --- a/cmd/buildah/commit.go +++ b/cmd/buildah/commit.go @@ -27,6 +27,7 @@ type commitInputOptions struct { blobCache string certDir string creds string + cwOptions string disableCompression bool format string iidfile string @@ -87,6 +88,7 @@ func commitListFlagSet(cmd *cobra.Command, opts *commitInputOptions) { _ = cmd.RegisterFlagCompletionFunc("cert-dir", completion.AutocompleteDefault) flags.StringVar(&opts.creds, "creds", "", "use `[username[:password]]` for accessing the registry") _ = cmd.RegisterFlagCompletionFunc("creds", completion.AutocompleteNone) + flags.StringVar(&opts.cwOptions, "cw", "", "confidential workload `options`") flags.BoolVarP(&opts.disableCompression, "disable-compression", "D", true, "don't compress layers") flags.StringVarP(&opts.format, "format", "f", defaultFormat(), "`format` of the image manifest and metadata") _ = cmd.RegisterFlagCompletionFunc("format", completion.AutocompleteNone) @@ -239,6 +241,14 @@ func commitCmd(c *cobra.Command, args []string, iopts commitInputOptions) error options.HistoryTimestamp = ×tamp } + if iopts.cwOptions != "" { + confidentialWorkloadOptions, err := parse.GetConfidentialWorkloadOptions(iopts.cwOptions) + if err != nil { + return fmt.Errorf("parsing --cw arguments: %w", err) + } + options.ConfidentialWorkloadOptions = confidentialWorkloadOptions + } + if exclusiveFlags > 1 { return errors.New("can not use more then one timestamp option at at time") } diff --git a/cmd/buildah/common.go b/cmd/buildah/common.go index 460994f434a..c684ca3a1a7 100644 --- a/cmd/buildah/common.go +++ b/cmd/buildah/common.go @@ -224,8 +224,8 @@ func Tail(a []string) []string { return []string{} } -// UsageTemplate returns the usage template for podman commands -// This blocks the displaying of the global options. The main podman +// UsageTemplate returns the usage template for buildah commands +// This blocks the displaying of the global options. The main buildah // command should not use this. func UsageTemplate() string { return `Usage:{{if .Runnable}} diff --git a/cmd/buildah/main.go b/cmd/buildah/main.go index 063b225a2f8..b482db517cb 100644 --- a/cmd/buildah/main.go +++ b/cmd/buildah/main.go @@ -104,7 +104,7 @@ func init() { rootCmd.PersistentFlags().StringSliceVar(&globalFlagResults.UserNSUID, "userns-uid-map", []string{}, "default `ctrID:hostID:length` UID mapping to use") rootCmd.PersistentFlags().StringSliceVar(&globalFlagResults.UserNSGID, "userns-gid-map", []string{}, "default `ctrID:hostID:length` GID mapping to use") rootCmd.PersistentFlags().StringVar(&globalFlagResults.DefaultMountsFile, "default-mounts-file", "", "path to default mounts file") - rootCmd.PersistentFlags().StringVar(&globalFlagResults.LogLevel, logLevel, "warn", `The log level to be used. Either "trace", "debug", "info", "warn", "error", "fatal", or "panic".`) + rootCmd.PersistentFlags().StringVar(&globalFlagResults.LogLevel, logLevel, "warn", `the log level to be used, one of "trace", "debug", "info", "warn", "error", "fatal", or "panic"`) rootCmd.PersistentFlags().StringVar(&globalFlagResults.CPUProfile, "cpu-profile", "", "`file` to write CPU profile") rootCmd.PersistentFlags().StringVar(&globalFlagResults.MemoryProfile, "memory-profile", "", "`file` to write memory profile") diff --git a/cmd/buildah/mkcw.go b/cmd/buildah/mkcw.go new file mode 100644 index 00000000000..273147f46ab --- /dev/null +++ b/cmd/buildah/mkcw.go @@ -0,0 +1,77 @@ +package main + +import ( + "fmt" + "os" + + "github.com/containers/buildah" + "github.com/containers/buildah/define" + "github.com/containers/buildah/pkg/parse" + "github.com/spf13/cobra" +) + +func mkcwCmd(c *cobra.Command, args []string, options buildah.CWConvertImageOptions) error { + ctx := getContext() + + systemContext, err := parse.SystemContextFromOptions(c) + if err != nil { + return err + } + + if options.AttestationURL == "" && options.DiskEncryptionPassphrase == "" { + return fmt.Errorf("neither --attestation-url nor --passphrase flags provided, disk would not be decryptable") + } + + store, err := getStore(c) + if err != nil { + return err + } + + options.InputImage = args[0] + options.Tag = args[1] + options.ReportWriter = os.Stderr + imageID, _, _, err := buildah.CWConvertImage(ctx, systemContext, store, options) + if err == nil { + fmt.Printf("%s\n", imageID) + } + return err +} + +func init() { + var teeType string + var options buildah.CWConvertImageOptions + mkcwDescription := `Convert a conventional image to a confidential workload image.` + mkcwCommand := &cobra.Command{ + Use: "mkcw", + Short: "Convert a conventional image to a confidential workload image", + Long: mkcwDescription, + RunE: func(cmd *cobra.Command, args []string) error { + options.TeeType = define.TeeType(teeType) + return mkcwCmd(cmd, args, options) + }, + Example: `buildah mkcw localhost/repository:typical localhost/repository:cw`, + Args: cobra.ExactArgs(2), + } + mkcwCommand.SetUsageTemplate(UsageTemplate()) + rootCmd.AddCommand(mkcwCommand) + flags := mkcwCommand.Flags() + flags.SetInterspersed(false) + + flags.StringVarP(&teeType, "type", "t", "", "TEE (trusted execution environment) type: SEV,SNP (default: SNP)") + flags.StringVarP(&options.AttestationURL, "attestation-url", "u", "", "attestation server URL") + flags.StringVarP(&options.BaseImage, "base-image", "b", "", "alternate base image (default: scratch)") + flags.StringVarP(&options.DiskEncryptionPassphrase, "passphrase", "p", "", "disk encryption passphrase") + flags.IntVarP(&options.CPUs, "cpus", "c", 0, "number of CPUs to expect") + flags.IntVarP(&options.Memory, "memory", "m", 0, "amount of memory to expect (MB)") + flags.StringVarP(&options.WorkloadID, "workload-id", "w", "", "workload ID") + flags.StringVarP(&options.Slop, "slop", "s", "25%", "extra space needed for converting a container rootfs to a disk image") + flags.StringVarP(&options.FirmwareLibrary, "firmware-library", "f", "", "location of libkrunfw-sev.so") + flags.BoolVarP(&options.IgnoreAttestationErrors, "ignore-attestation-errors", "", false, "ignore attestation errors") + if err := flags.MarkHidden("ignore-attestation-errors"); err != nil { + panic(fmt.Sprintf("error marking ignore-attestation-errors as hidden: %v", err)) + } + flags.String("signature-policy", "", "`pathname` of signature policy file (not usually used)") + if err := flags.MarkHidden("signature-policy"); err != nil { + panic(fmt.Sprintf("error marking signature-policy as hidden: %v", err)) + } +} diff --git a/commit.go b/commit.go index 21ec6ea542c..1268181d82b 100644 --- a/commit.go +++ b/commit.go @@ -105,6 +105,10 @@ type CommitOptions struct { // integers in the slice represent 0-indexed layer indices, with support for negative // indexing. i.e. 0 is the first layer, -1 is the last (top-most) layer. OciEncryptLayers *[]int + // ConfidentialWorkloadOptions is used to force the output image's rootfs to contain a + // LUKS-compatibly encrypted disk image (for use with krun) instead of the usual + // contents of a rootfs. + ConfidentialWorkloadOptions ConfidentialWorkloadOptions // UnsetEnvs is a list of environments to not add to final image. // Deprecated: use UnsetEnv() before committing instead. UnsetEnvs []string diff --git a/convertcw.go b/convertcw.go new file mode 100644 index 00000000000..85576f425ab --- /dev/null +++ b/convertcw.go @@ -0,0 +1,217 @@ +package buildah + +import ( + "context" + "fmt" + "io" + "time" + + "github.com/containers/buildah/define" + "github.com/containers/buildah/internal/mkcw" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/types" + encconfig "github.com/containers/ocicrypt/config" + "github.com/containers/storage" + "github.com/containers/storage/pkg/archive" + "github.com/opencontainers/go-digest" + "github.com/sirupsen/logrus" +) + +// CWConvertImageOptions provides both required and optional bits of +// configuration for CWConvertImage(). +type CWConvertImageOptions struct { + // Required parameters. + InputImage string + + // If supplied, we'll tag the resulting image with the specified name. + Tag string + OutputImage types.ImageReference + + // If supplied, we'll register the workload with this server. + // Practically necessary if DiskEncryptionPassphrase is not set, in + // which case we'll generate one and throw it away after. + AttestationURL string + + // Used to measure the environment. If left unset (0), defaults will be applied. + CPUs int + Memory int + + // Can be manually set. If left unset ("", false, nil), reasonable values will be used. + TeeType define.TeeType + IgnoreAttestationErrors bool + WorkloadID string + DiskEncryptionPassphrase string + Slop string + FirmwareLibrary string + BaseImage string + Logger *logrus.Logger + + // Passed through to BuilderOptions. Most settings won't make + // sense to be made available here because we don't launch a process. + ContainerSuffix string + PullPolicy PullPolicy + BlobDirectory string + SignaturePolicyPath string + ReportWriter io.Writer + IDMappingOptions *IDMappingOptions + Format string + MaxPullRetries int + PullRetryDelay time.Duration + OciDecryptConfig *encconfig.DecryptConfig + MountLabel string +} + +// CWConvertImage takes the rootfs and configuration from one image, generates a +// LUKS-encrypted disk image that more or less includes them both, and puts the +// result into a new container image. +// Returns the new image's ID and digest on success, along with a canonical +// reference for it if a repository name was specified. +func CWConvertImage(ctx context.Context, systemContext *types.SystemContext, store storage.Store, options CWConvertImageOptions) (string, reference.Canonical, digest.Digest, error) { + // Apply our defaults if some options aren't set. + logger := options.Logger + if logger == nil { + logger = logrus.StandardLogger() + } + + // Now create the target working container, pulling the base image if + // there is one and it isn't present. + builderOptions := BuilderOptions{ + FromImage: options.BaseImage, + SystemContext: systemContext, + Logger: logger, + + ContainerSuffix: options.ContainerSuffix, + PullPolicy: options.PullPolicy, + BlobDirectory: options.BlobDirectory, + SignaturePolicyPath: options.SignaturePolicyPath, + ReportWriter: options.ReportWriter, + IDMappingOptions: options.IDMappingOptions, + Format: options.Format, + MaxPullRetries: options.MaxPullRetries, + PullRetryDelay: options.PullRetryDelay, + OciDecryptConfig: options.OciDecryptConfig, + MountLabel: options.MountLabel, + } + target, err := NewBuilder(ctx, store, builderOptions) + if err != nil { + return "", nil, "", fmt.Errorf("creating container from target image: %w", err) + } + defer func() { + if err := target.Delete(); err != nil { + logrus.Warnf("deleting target container: %v", err) + } + }() + targetDir, err := target.Mount("") + if err != nil { + return "", nil, "", fmt.Errorf("mounting target container: %w", err) + } + defer func() { + if err := target.Unmount(); err != nil { + logrus.Warnf("unmounting target container: %v", err) + } + }() + + // Mount the source image, pulling it first if necessary. + builderOptions = BuilderOptions{ + FromImage: options.InputImage, + SystemContext: systemContext, + Logger: logger, + + ContainerSuffix: options.ContainerSuffix, + PullPolicy: options.PullPolicy, + BlobDirectory: options.BlobDirectory, + SignaturePolicyPath: options.SignaturePolicyPath, + ReportWriter: options.ReportWriter, + IDMappingOptions: options.IDMappingOptions, + Format: options.Format, + MaxPullRetries: options.MaxPullRetries, + PullRetryDelay: options.PullRetryDelay, + OciDecryptConfig: options.OciDecryptConfig, + MountLabel: options.MountLabel, + } + source, err := NewBuilder(ctx, store, builderOptions) + if err != nil { + return "", nil, "", fmt.Errorf("creating container from source image: %w", err) + } + defer func() { + if err := source.Delete(); err != nil { + logrus.Warnf("deleting source container: %v", err) + } + }() + sourceInfo := GetBuildInfo(source) + if err != nil { + return "", nil, "", fmt.Errorf("retrieving info about source image: %w", err) + } + sourceImageID := sourceInfo.FromImageID + sourceSize, err := store.ImageSize(sourceImageID) + if err != nil { + return "", nil, "", fmt.Errorf("computing size of source image: %w", err) + } + sourceDir, err := source.Mount("") + if err != nil { + return "", nil, "", fmt.Errorf("mounting source container: %w", err) + } + defer func() { + if err := source.Unmount(); err != nil { + logrus.Warnf("unmounting source container: %v", err) + } + }() + + // Generate the image contents. + archiveOptions := mkcw.ArchiveOptions{ + AttestationURL: options.AttestationURL, + CPUs: options.CPUs, + Memory: options.Memory, + TempDir: targetDir, + TeeType: options.TeeType, + IgnoreAttestationErrors: options.IgnoreAttestationErrors, + ImageSize: sourceSize, + WorkloadID: options.WorkloadID, + DiskEncryptionPassphrase: options.DiskEncryptionPassphrase, + Slop: options.Slop, + FirmwareLibrary: options.FirmwareLibrary, + Logger: logger, + } + rc, workloadConfig, err := mkcw.Archive(sourceDir, &source.OCIv1, archiveOptions) + if err != nil { + return "", nil, "", fmt.Errorf("generating encrypted image content: %w", err) + } + if err = archive.Untar(rc, targetDir, &archive.TarOptions{}); err != nil { + if err = rc.Close(); err != nil { + logger.Warnf("cleaning up: %v", err) + } + return "", nil, "", fmt.Errorf("saving encrypted image content: %w", err) + } + if err = rc.Close(); err != nil { + return "", nil, "", fmt.Errorf("cleaning up: %w", err) + } + + // Commit the image. Clear out most of the configuration (if there is any — we default + // to scratch as a base) so that an engine that doesn't or can't set up a TEE will just + // run the static entrypoint. The rest of the configuration which the runtime consults + // is in the .krun_config.json file in the encrypted filesystem. + logger.Log(logrus.DebugLevel, "committing disk image") + target.ClearAnnotations() + target.ClearEnv() + target.ClearLabels() + target.ClearOnBuild() + target.ClearPorts() + target.ClearVolumes() + target.SetCmd(nil) + target.SetCreatedBy(fmt.Sprintf(": convert %q for use with %q", sourceImageID, workloadConfig.Type)) + target.SetDomainname("") + target.SetEntrypoint([]string{"/entrypoint"}) + target.SetHealthcheck(nil) + target.SetHostname("") + target.SetMaintainer("") + target.SetShell(nil) + target.SetUser("") + target.SetWorkDir("") + commitOptions := CommitOptions{ + SystemContext: systemContext, + } + if options.Tag != "" { + commitOptions.AdditionalTags = append(commitOptions.AdditionalTags, options.Tag) + } + return target.Commit(ctx, options.OutputImage, commitOptions) +} diff --git a/convertcw_test.go b/convertcw_test.go new file mode 100644 index 00000000000..7e5263935e0 --- /dev/null +++ b/convertcw_test.go @@ -0,0 +1,163 @@ +package buildah + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "net/http" + "os" + "path/filepath" + "sync" + "testing" + + "github.com/containers/buildah/internal/mkcw" + mkcwtypes "github.com/containers/buildah/internal/mkcw/types" + "github.com/containers/image/v5/types" + "github.com/containers/storage" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// dummyAttestationHandler replies with a fixed response code to requests to +// the right path, and caches passphrases indexed by workload ID +type dummyAttestationHandler struct { + t *testing.T + status int + passphrases map[string]string + passphrasesLock sync.Mutex +} + +func (d *dummyAttestationHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + var body bytes.Buffer + if req.Body != nil { + if _, err := io.Copy(&body, req.Body); err != nil { + d.t.Logf("reading request body: %v", err) + return + } + req.Body.Close() + } + if req.URL != nil && req.URL.Path == "/kbs/v0/register_workload" { + var registrationRequest mkcwtypes.RegistrationRequest + // if we can't decode the client request, bail + if err := json.Unmarshal(body.Bytes(), ®istrationRequest); err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } + // cache the passphrase + d.passphrasesLock.Lock() + if d.passphrases == nil { + d.passphrases = make(map[string]string) + } + d.passphrases[registrationRequest.WorkloadID] = registrationRequest.Passphrase + d.passphrasesLock.Unlock() + // return the predetermined status + status := d.status + if status == 0 { + status = http.StatusOK + } + rw.WriteHeader(status) + return + } + // no such handler + rw.WriteHeader(http.StatusInternalServerError) +} + +func TestCWConvertImage(t *testing.T) { + ctx := context.TODO() + systemContext := &types.SystemContext{} + for _, status := range []int{http.StatusOK, http.StatusInternalServerError} { + for _, ignoreChainRetrievalErrors := range []bool{false, true} { + for _, ignoreAttestationErrors := range []bool{false, true} { + t.Run(fmt.Sprintf("status=%d,ignoreChainRetrievalErrors=%v,ignoreAttestationErrors=%v", status, ignoreChainRetrievalErrors, ignoreAttestationErrors), func(t *testing.T) { + // create a per-test Store object + storeOptions := storage.StoreOptions{ + GraphRoot: t.TempDir(), + RunRoot: t.TempDir(), + GraphDriverName: "vfs", + } + store, err := storage.GetStore(storeOptions) + require.NoError(t, err) + t.Cleanup(func() { + if _, err := store.Shutdown(true); err != nil { + t.Logf("store.Shutdown(%q): %v", t.Name(), err) + } + }) + // listen on a system-assigned port + listener, err := net.Listen("tcp", ":0") + require.NoError(t, err) + // keep track of our listener address + addr := listener.Addr() + // serve requests on that listener + handler := &dummyAttestationHandler{t: t, status: status} + server := http.Server{ + Handler: handler, + } + go func() { + if err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { + t.Logf("serve: %v", err) + } + }() + // clean up at the end of this test + t.Cleanup(func() { assert.NoError(t, server.Close()) }) + // convert an image + options := CWConvertImageOptions{ + InputImage: "docker.io/library/busybox", + Tag: "localhost/busybox:encrypted", + AttestationURL: "http://" + addr.String(), + IgnoreAttestationErrors: ignoreAttestationErrors, + Slop: "16MB", + } + id, _, _, err := CWConvertImage(ctx, systemContext, store, options) + if status != http.StatusOK && !ignoreAttestationErrors { + assert.Error(t, err) + return + } + if ignoreChainRetrievalErrors && ignoreAttestationErrors { + assert.NoError(t, err) + } + if err != nil { + t.Skipf("%s: %v", t.Name(), err) + return + } + // mount the image + path, err := store.MountImage(id, nil, "") + require.NoError(t, err) + t.Cleanup(func() { + if _, err := store.UnmountImage(id, true); err != nil { + t.Logf("store.UnmountImage(%q): %v", t.Name(), err) + } + }) + // check that the image's contents look like what we expect: disk + disk := filepath.Join(path, "disk.img") + require.FileExists(t, disk) + workloadConfig, err := mkcw.ReadWorkloadConfigFromImage(disk) + require.NoError(t, err) + handler.passphrasesLock.Lock() + decryptionPassphrase := handler.passphrases[workloadConfig.WorkloadID] + handler.passphrasesLock.Unlock() + err = mkcw.CheckLUKSPassphrase(disk, decryptionPassphrase) + assert.NoError(t, err) + // check that the image's contents look like what we expect: config file + config := filepath.Join(path, "krun-sev.json") + require.FileExists(t, config) + workloadConfigBytes, err := os.ReadFile(config) + require.NoError(t, err) + var workloadConfigTwo mkcwtypes.WorkloadConfig + err = json.Unmarshal(workloadConfigBytes, &workloadConfigTwo) + require.NoError(t, err) + assert.Equal(t, workloadConfig, workloadConfigTwo) + // check that the image's contents look like what we expect: an executable entry point + entrypoint := filepath.Join(path, "entrypoint") + require.FileExists(t, entrypoint) + st, err := os.Stat(entrypoint) + require.NoError(t, err) + assert.Equal(t, st.Mode().Type(), os.FileMode(0)) // regular file + }) + } + } + } +} diff --git a/define/build.go b/define/build.go index 4a8ed683f6d..ce5ad0ade46 100644 --- a/define/build.go +++ b/define/build.go @@ -163,6 +163,10 @@ type BuildOptions struct { // It allows end user to export recently built rootfs into a directory or tar. // See the documentation of 'buildah build --output' for the details of the format. BuildOutput string + // ConfidentialWorkload controls whether or not, and if so, how, we produce an + // image that's meant to be run using krun as a VM instead of a conventional + // process-type container. + ConfidentialWorkload ConfidentialWorkloadOptions // Additional tags to add to the image that we write, if we know of a // way to add them. AdditionalTags []string diff --git a/define/types.go b/define/types.go index 0e8838764da..01d38544d0d 100644 --- a/define/types.go +++ b/define/types.go @@ -47,8 +47,16 @@ const ( OCI = "oci" // DOCKER used to define the "docker" image format DOCKER = "docker" + + // SEV is a known trusted execution environment type: AMD-SEV (secure encrypted virtualization using encrypted state, requires epyc 1000 "naples") + SEV TeeType = "sev" + // SNP is a known trusted execution environment type: AMD-SNP (SEV secure nested pages) (requires epyc 3000 "milan") + SNP TeeType = "snp" ) +// TeeType is a supported trusted execution environment type. +type TeeType string + var ( // DefaultCapabilities is the list of capabilities which we grant by // default to containers which are running under UID 0. @@ -105,6 +113,23 @@ type BuildOutputOption struct { IsStdout bool } +// ConfidentialWorkloadOptions encapsulates options which control whether or not +// we output an image whose rootfs contains a LUKS-compatibly-encrypted disk image +// instead of the usual rootfs contents. +type ConfidentialWorkloadOptions struct { + Convert bool + AttestationURL string + CPUs int + Memory int + TempDir string + TeeType TeeType + IgnoreAttestationErrors bool + WorkloadID string + DiskEncryptionPassphrase string + Slop string + FirmwareLibrary string +} + // TempDirForURL checks if the passed-in string looks like a URL or -. If it is, // TempDirForURL creates a temporary directory, arranges for its contents to be // the contents of that URL, and returns the temporary directory's path, along diff --git a/docs/buildah-build.1.md b/docs/buildah-build.1.md index 8507439d0e7..511eeee9a99 100644 --- a/docs/buildah-build.1.md +++ b/docs/buildah-build.1.md @@ -288,6 +288,62 @@ The [username[:password]] to use to authenticate with the registry if required. If one or both values are not supplied, a command line prompt will appear and the value can be entered. The password is entered without echo. +**--cw** *options* + +Produce an image suitable for use as a confidential workload running in a +trusted execution environment (TEE) using krun (i.e., *crun* built with the +libkrun feature enabled and invoked as *krun*). Instead of the conventional +contents, the root filesystem of the image will contain an encrypted disk image +and configuration information for krun. + +The value for *options* is a comma-separated list of key=value pairs, supplying +configuration information which is needed for producing the additional data +which will be included in the container image. + +Recognized _keys_ are: + +*attestation_url*: The location of a key broker / attestation server. +If a value is specified, the new image's workload ID, along with the passphrase +used to encrypt the disk image, will be registered with the server, and the +server's location will be stored in the container image. +At run-time, krun is expected to contact the server to retrieve the passphrase +using the workload ID, which is also stored in the container image. +If no value is specified, a *passphrase* value *must* be specified. + +*cpus*: The number of virtual CPUs which the image expects to be run with at +run-time. If not specified, a default value will be supplied. + +*firmware_library*: The location of the libkrunfw-sev shared library. If not +specified, `buildah` checks for its presence in a number of hard-coded +locations. + +*memory*: The amount of memory which the image expects to be run with at +run-time, as a number of megabytes. If not specified, a default value will be +supplied. + +*passphrase*: The passphrase to use to encrypt the disk image which will be +included in the container image. +If no value is specified, but an *attestation_url* value is specified, a +randomly-generated passphrase will be used. +The authors recommend setting an *attestation_url* but not a *passphrase*. + +*slop*: Extra space to allocate for the disk image compared to the size of the +container image's contents, expressed either as a percentage (..%) or a size +value (bytes, or larger units if suffixes like KB or MB are present), or a sum +of two or more such specifications. If not specified, `buildah` guesses that +25% more space than the contents will be enough, but this option is provided in +case its guess is wrong. + +*type*: The type of trusted execution environment (TEE) which the image should +be marked for use with. Accepted values are "SEV" (AMD Secure Encrypted +Virtualization - Encrypted State) and "SNP" (AMD Secure Encrypted +Virtualization - Secure Nested Paging). If not specified, defaults to "SNP". + +*workload_id*: A workload identifier which will be recorded in the container +image, to be used at run-time for retrieving the passphrase which was used to +encrypt the disk image. If not specified, a semi-random value will be derived +from the base image's image ID. + **--decryption-key** *key[:passphrase]* The [key[:passphrase]] to be used for decryption of images. Key can point to keys and/or certificates. Decryption will be tried with all keys. If the key is protected by a passphrase, it is required to be passed in the argument and omitted otherwise. diff --git a/docs/buildah-commit.1.md b/docs/buildah-commit.1.md index 2e5a4aa6260..72f8aa9d39e 100644 --- a/docs/buildah-commit.1.md +++ b/docs/buildah-commit.1.md @@ -39,6 +39,63 @@ The [username[:password]] to use to authenticate with the registry if required. If one or both values are not supplied, a command line prompt will appear and the value can be entered. The password is entered without echo. +**--cw** *options* + +Produce an image suitable for use as a confidential workload running in a +trusted execution environment (TEE) using krun (i.e., *crun* built with the +libkrun feature enabled and invoked as *krun*). Instead of the conventional +contents, the root filesystem of the image will contain an encrypted disk image +and configuration information for krun. + +The value for *options* is a comma-separated list of key=value pairs, supplying +configuration information which is needed for producing the additional data +which will be included in the container image. + +Recognized _keys_ are: + +*attestation_url*: The location of a key broker / attestation server. +If a value is specified, the new image's workload ID, along with the passphrase +used to encrypt the disk image, will be registered with the server, and the +server's location will be stored in the container image. +At run-time, krun is expected to contact the server to retrieve the passphrase +using the workload ID, which is also stored in the container image. +If no value is specified, a *passphrase* value *must* be specified. + +*cpus*: The number of virtual CPUs which the image expects to be run with at +run-time. If not specified, a default value will be supplied. + +*firmware_library*: The location of the libkrunfw-sev shared library. If not +specified, `buildah` checks for its presence in a number of hard-coded +locations. + +*memory*: The amount of memory which the image expects to be run with at +run-time, as a number of megabytes. If not specified, a default value will be +supplied. + +*passphrase*: The passphrase to use to encrypt the disk image which will be +included in the container image. +If no value is specified, but an *attestation_url* value is specified, a +randomly-generated passphrase will be used. +The authors recommend setting an *attestation_url* but not a *passphrase*. + +*slop*: Extra space to allocate for the disk image compared to the size of the +container image's contents, expressed either as a percentage (..%) or a size +value (bytes, or larger units if suffixes like KB or MB are present), or a sum +of two or more such specifications separated by "+". If not specified, +`buildah` guesses that 25% more space than the contents will be enough, but +this option is provided in case its guess is wrong. If the specified or +computed size is less than 10 megabytes, it will be increased to 10 megabytes. + +*type*: The type of trusted execution environment (TEE) which the image should +be marked for use with. Accepted values are "SEV" (AMD Secure Encrypted +Virtualization - Encrypted State) and "SNP" (AMD Secure Encrypted +Virtualization - Secure Nested Paging). If not specified, defaults to "SNP". + +*workload_id*: A workload identifier which will be recorded in the container +image, to be used at run-time for retrieving the passphrase which was used to +encrypt the disk image. If not specified, a semi-random value will be derived +from the base image's image ID. + **--disable-compression**, **-D** Don't compress filesystem layers when building the image unless it is required diff --git a/docs/buildah-mkcw.1.md b/docs/buildah-mkcw.1.md new file mode 100644 index 00000000000..2733327451c --- /dev/null +++ b/docs/buildah-mkcw.1.md @@ -0,0 +1,78 @@ +# buildah-mkcw "1" "July 2023" "buildah" + +## NAME +buildah\-mkcw - Convert a conventional container image into a confidential workload image. + +## SYNOPSIS +**buildah mkcw** [*options*] *source* *destination* + +## DESCRIPTION +Converts the contents of a container image into a new container image which is +suitable for use in a trusted execution environment (TEE), typically run using +krun (i.e., crun built with the libkrun feature enabled and invoked as *krun*). +Instead of the conventional contents, the root filesystem of the created image +will contain an encrypted disk image and configuration information for krun. + +## source +A container image, stored locally or in a registry + +## destination +A container image, stored locally or in a registry + +## OPTIONS + +**--attestation-url**, **-u** *url* +The location of a key broker / attestation server. +If a value is specified, the new image's workload ID, along with the passphrase +used to encrypt the disk image, will be registered with the server, and the +server's location will be stored in the container image. +At run-time, krun is expected to contact the server to retrieve the passphrase +using the workload ID, which is also stored in the container image. +If no value is specified, a *passphrase* value *must* be specified. + +**--base-image**, **-b** *image* +An alternate image to use as the base for the output image. By default, +the *scratch* non-image is used. + +**--cpus**, **-c** *number* +The number of virtual CPUs which the image expects to be run with at run-time. +If not specified, a default value will be supplied. + +**--firmware-library**, **-f** *file* +The location of the libkrunfw-sev shared library. If not specified, `buildah` +checks for its presence in a number of hard-coded locations. + +**--memory**, **-m** *number* +The amount of memory which the image expects to be run with at run-time, as a +number of megabytes. If not specified, a default value will be supplied. + +**--passphrase**, **-p** *text* +The passphrase to use to encrypt the disk image which will be included in the +container image. +If no value is specified, but an *--attestation-url* value is specified, a +randomly-generated passphrase will be used. +The authors recommend setting an *--attestation-url* but not a *--passphrase*. + +**--slop**, **-s** *{percentage%|sizeKB|sizeMB|sizeGB}* +Extra space to allocate for the disk image compared to the size of the +container image's contents, expressed either as a percentage (..%) or a size +value (bytes, or larger units if suffixes like KB or MB are present), or a sum +of two or more such specifications. If not specified, `buildah` guesses that +25% more space than the contents will be enough, but this option is provided in +case its guess is wrong. If the specified or computed size is less than 10 +megabytes, it will be increased to 10 megabytes. + +**--type**, **-t** {SEV|SNP} +The type of trusted execution environment (TEE) which the image should be +marked for use with. Accepted values are "SEV" (AMD Secure Encrypted +Virtualization - Encrypted State) and "SNP" (AMD Secure Encrypted +Virtualization - Secure Nested Paging). If not specified, defaults to "SNP". + +**--workload-id**, **-w** *id* +A workload identifier which will be recorded in the container image, to be used +at run-time for retrieving the passphrase which was used to encrypt the disk +image. If not specified, a semi-random value will be derived from the base +image's image ID. + +## SEE ALSO +buildah(1) diff --git a/docs/buildah.1.md b/docs/buildah.1.md index 3ca62b7c14e..1f8bbc5f14e 100644 --- a/docs/buildah.1.md +++ b/docs/buildah.1.md @@ -158,6 +158,7 @@ Buildah can set up environment variables from the env entry in the [engine] tabl | login | [buildah-login(1)](buildah-login.1.md) | Login to a container registry. | | logout | [buildah-logout(1)](buildah-logout.1.md) | Logout of a container registry | | manifest | [buildah-manifest(1)](buildah-manifest.1.md) | Create and manipulate manifest lists and image indexes. | +| mkcw | [buildah-mkcw(1)](buildah-mkcw.1.md) | Convert a conventional container image into a confidential workload image. | mount | [buildah-mount(1)](buildah-mount.1.md) | Mount the working container's root filesystem. | | prune | [buildah-prune(1)](buildah-prune.1.md) | Cleanup intermediate images as well as build and mount cache. | | pull | [buildah-pull(1)](buildah-pull.1.md) | Pull an image from the specified location. | diff --git a/go.mod b/go.mod index fc95d706bc6..1c1bde423f4 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/containernetworking/plugins v1.3.0 github.com/containers/common v0.55.1-0.20230830075933-12405381ff45 github.com/containers/image/v5 v5.26.1-0.20230807184415-3fb422379cfa + github.com/containers/luksy v0.0.0-20230808154129-d2d74a56682f github.com/containers/ocicrypt v1.1.8 github.com/containers/storage v1.49.0 github.com/cyphar/filepath-securejoin v0.2.4 @@ -48,6 +49,7 @@ require ( github.com/Microsoft/hcsshim v0.10.0 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect + github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/container-orchestrated-devices/container-device-interface v0.6.0 // indirect diff --git a/go.sum b/go.sum index 3975cecc242..d383ba31cb5 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,8 @@ github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1o github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= +github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 h1:5L8Mj9Co9sJVgW3TpYk2gxGJnDjsYuboNTcRmbtGKGs= +github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6/go.mod h1:3HgLJ9d18kXMLQlJvIY3+FszZYMxCz8WfE2MQ7hDY0w= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= @@ -56,6 +58,8 @@ github.com/containers/image/v5 v5.26.1-0.20230807184415-3fb422379cfa h1:wDfVQtc6 github.com/containers/image/v5 v5.26.1-0.20230807184415-3fb422379cfa/go.mod h1:apL4qwq31NV0gsSZQJPxYyTH0yzWavmMCjT8vsQaXSk= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= +github.com/containers/luksy v0.0.0-20230808154129-d2d74a56682f h1:/HjLNYkVoUJNT4mm2dzGl63x7nD6YHxxI/k1kR0TkzA= +github.com/containers/luksy v0.0.0-20230808154129-d2d74a56682f/go.mod h1:hEjwW0sePqkTahMzbzeDsQEXN2zdF2VAccqSj5vb1NY= github.com/containers/ocicrypt v1.1.8 h1:saSBF0/8DyPUjzcxMVzL2OBUWCkvRvqIm75pu0ADSZk= github.com/containers/ocicrypt v1.1.8/go.mod h1:jM362hyBtbwLMWzXQZTlkjKGAQf/BN/LFMtH0FIRt34= github.com/containers/storage v1.49.0 h1:7Vqj8OKlwlcWZ4U61+eS2bNwyIG/qXQo/UZVW5kXn74= diff --git a/image.go b/image.go index 01c1784d2fd..1a48f5b74e1 100644 --- a/image.go +++ b/image.go @@ -16,6 +16,7 @@ import ( "github.com/containers/buildah/copier" "github.com/containers/buildah/define" "github.com/containers/buildah/docker" + "github.com/containers/buildah/internal/mkcw" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/image" "github.com/containers/image/v5/manifest" @@ -69,6 +70,7 @@ type containerImageRef struct { annotations map[string]string preferredManifestType string squash bool + confidentialWorkload ConfidentialWorkloadOptions omitHistory bool emptyLayer bool idMappingOptions *define.IDMappingOptions @@ -158,6 +160,52 @@ func computeLayerMIMEType(what string, layerCompression archive.Compression) (om return omediaType, dmediaType, nil } +// Extract the container's whole filesystem as a filesystem image, wrapped +// in LUKS-compatible encryption. +func (i *containerImageRef) extractConfidentialWorkloadFS(options ConfidentialWorkloadOptions) (io.ReadCloser, error) { + var image v1.Image + if err := json.Unmarshal(i.oconfig, &image); err != nil { + return nil, fmt.Errorf("recreating OCI configuration for %q: %w", i.containerID, err) + } + mountPoint, err := i.store.Mount(i.containerID, i.mountLabel) + if err != nil { + return nil, fmt.Errorf("mounting container %q: %w", i.containerID, err) + } + archiveOptions := mkcw.ArchiveOptions{ + AttestationURL: options.AttestationURL, + CPUs: options.CPUs, + Memory: options.Memory, + TempDir: options.TempDir, + TeeType: options.TeeType, + IgnoreAttestationErrors: options.IgnoreAttestationErrors, + WorkloadID: options.WorkloadID, + DiskEncryptionPassphrase: options.DiskEncryptionPassphrase, + Slop: options.Slop, + FirmwareLibrary: options.FirmwareLibrary, + } + rc, _, err := mkcw.Archive(mountPoint, &image, archiveOptions) + if err != nil { + if _, err2 := i.store.Unmount(i.containerID, false); err2 != nil { + logrus.Debugf("unmounting container %q: %v", i.containerID, err2) + } + return nil, fmt.Errorf("converting rootfs %q: %w", i.containerID, err) + } + return ioutils.NewReadCloserWrapper(rc, func() error { + if err = rc.Close(); err != nil { + err = fmt.Errorf("closing tar archive of container %q: %w", i.containerID, err) + } + if _, err2 := i.store.Unmount(i.containerID, false); err == nil { + if err2 != nil { + err2 = fmt.Errorf("unmounting container %q: %w", i.containerID, err2) + } + err = err2 + } else { + logrus.Debugf("unmounting container %q: %v", i.containerID, err2) + } + return err + }), nil +} + // Extract the container's whole filesystem as if it were a single layer. // Takes ExtractRootfsOptions as argument which allows caller to configure // preserve nature of setuid,setgid,sticky and extended attributes @@ -221,7 +269,7 @@ func (i *containerImageRef) createConfigsAndManifests() (v1.Image, v1.Manifest, oimage.RootFS.DiffIDs = []digest.Digest{} // Only clear the history if we're squashing, otherwise leave it be so that we can append // entries to it. - if i.squash || i.omitHistory { + if i.confidentialWorkload.Convert || i.squash || i.omitHistory { oimage.History = []v1.History{} } @@ -237,6 +285,24 @@ func (i *containerImageRef) createConfigsAndManifests() (v1.Image, v1.Manifest, } // Always replace this value, since we're newer than our base image. dimage.Created = created + // If we're producing a confidential workload, override the command and + // assorted other settings that aren't expected to work correctly. + if i.confidentialWorkload.Convert { + dimage.Config.Entrypoint = []string{"/entrypoint"} + oimage.Config.Entrypoint = []string{"/entrypoint"} + dimage.Config.Cmd = nil + oimage.Config.Cmd = nil + dimage.Config.User = "" + oimage.Config.User = "" + dimage.Config.WorkingDir = "" + oimage.Config.WorkingDir = "" + dimage.Config.Healthcheck = nil + dimage.Config.Shell = nil + dimage.Config.Volumes = nil + oimage.Config.Volumes = nil + dimage.Config.ExposedPorts = nil + oimage.Config.ExposedPorts = nil + } // Clear the list of diffIDs, since we always repopulate it. dimage.RootFS = &docker.V2S2RootFS{} dimage.RootFS.Type = docker.TypeLayers @@ -244,7 +310,7 @@ func (i *containerImageRef) createConfigsAndManifests() (v1.Image, v1.Manifest, // Only clear the history if we're squashing, otherwise leave it be so // that we can append entries to it. Clear the parent, too, we no // longer include its layers and history. - if i.squash || i.omitHistory { + if i.confidentialWorkload.Convert || i.squash || i.omitHistory { dimage.Parent = "" dimage.History = []docker.V2S2History{} } @@ -296,7 +362,7 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System for layer != nil { layers = append(append([]string{}, layerID), layers...) layerID = layer.Parent - if layerID == "" || i.squash { + if layerID == "" || i.confidentialWorkload.Convert || i.squash { err = nil break } @@ -333,7 +399,7 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System blobLayers := make(map[digest.Digest]blobLayerInfo) for _, layerID := range layers { what := fmt.Sprintf("layer %q", layerID) - if i.squash { + if i.confidentialWorkload.Convert || i.squash { what = fmt.Sprintf("container %q", i.containerID) } // The default layer media type assumes no compression. @@ -351,7 +417,7 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System } // If we already know the digest of the contents of parent // layers, reuse their blobsums, diff IDs, and sizes. - if !i.squash && layerID != i.layerID && layer.UncompressedDigest != "" { + if !i.confidentialWorkload.Convert && !i.squash && layerID != i.layerID && layer.UncompressedDigest != "" { layerBlobSum := layer.UncompressedDigest layerBlobSize := layer.UncompressedSize diffID := layer.UncompressedDigest @@ -389,7 +455,13 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System } var rc io.ReadCloser var errChan chan error - if i.squash { + if i.confidentialWorkload.Convert { + // Convert the root filesystem into an encrypted disk image. + rc, err = i.extractConfidentialWorkloadFS(i.confidentialWorkload) + if err != nil { + return nil, err + } + } else if i.squash { // Extract the root filesystem as a single layer. rc, errChan, err = i.extractRootfs(ExtractRootfsOptions{}) if err != nil { @@ -842,6 +914,7 @@ func (b *Builder) makeContainerImageRef(options CommitOptions) (*containerImageR annotations: b.Annotations(), preferredManifestType: manifestType, squash: options.Squash, + confidentialWorkload: options.ConfidentialWorkloadOptions, omitHistory: options.OmitHistory, emptyLayer: options.EmptyLayer && !options.Squash, idMappingOptions: &b.IDMappingOptions, diff --git a/imagebuildah/executor.go b/imagebuildah/executor.go index 2b9773048f1..f61c2d1f78c 100644 --- a/imagebuildah/executor.go +++ b/imagebuildah/executor.go @@ -148,6 +148,7 @@ type Executor struct { osVersion string osFeatures []string envs []string + confidentialWorkload define.ConfidentialWorkloadOptions } type imageTypeAndHistoryAndDiffIDs struct { @@ -303,6 +304,7 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o osVersion: options.OSVersion, osFeatures: append([]string{}, options.OSFeatures...), envs: append([]string{}, options.Envs...), + confidentialWorkload: options.ConfidentialWorkload, } if exec.err == nil { exec.err = os.Stderr diff --git a/imagebuildah/stage_executor.go b/imagebuildah/stage_executor.go index 942dcaa8a0b..0c722a1dfe6 100644 --- a/imagebuildah/stage_executor.go +++ b/imagebuildah/stage_executor.go @@ -1032,7 +1032,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string, // squash the contents of the base image. Whichever is // the case, we need to commit() to create a new image. logCommit(s.output, -1) - if imgID, ref, err = s.commit(ctx, s.getCreatedBy(nil, ""), false, s.output, s.executor.squash); err != nil { + if imgID, ref, err = s.commit(ctx, s.getCreatedBy(nil, ""), false, s.output, s.executor.squash, lastStage); err != nil { return "", nil, fmt.Errorf("committing base container: %w", err) } // Generate build output if needed. @@ -1045,7 +1045,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string, // The image would be modified by the labels passed // via the command line, so we need to commit. logCommit(s.output, -1) - if imgID, ref, err = s.commit(ctx, s.getCreatedBy(stage.Node, ""), true, s.output, s.executor.squash); err != nil { + if imgID, ref, err = s.commit(ctx, s.getCreatedBy(stage.Node, ""), true, s.output, s.executor.squash, lastStage); err != nil { return "", nil, err } // Generate build output if needed. @@ -1193,7 +1193,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string, // stage. if lastStage || imageIsUsedLater { logCommit(s.output, i) - imgID, ref, err = s.commit(ctx, s.getCreatedBy(node, addedContentSummary), false, s.output, s.executor.squash) + imgID, ref, err = s.commit(ctx, s.getCreatedBy(node, addedContentSummary), false, s.output, s.executor.squash, lastStage && lastInstruction) if err != nil { return "", nil, fmt.Errorf("committing container for step %+v: %w", *step, err) } @@ -1420,7 +1420,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string, // because at this point we want to save history for // layers even if its a squashed build so that they // can be part of build-cache. - imgID, ref, err = s.commit(ctx, s.getCreatedBy(node, addedContentSummary), !s.stepRequiresLayer(step), commitName, false) + imgID, ref, err = s.commit(ctx, s.getCreatedBy(node, addedContentSummary), !s.stepRequiresLayer(step), commitName, false, lastStage && lastInstruction) if err != nil { return "", nil, fmt.Errorf("committing container for step %+v: %w", *step, err) } @@ -1454,7 +1454,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string, // Create a squashed version of this image // if we're supposed to create one and this // is the last instruction of the last stage. - imgID, ref, err = s.commit(ctx, s.getCreatedBy(node, addedContentSummary), !s.stepRequiresLayer(step), commitName, true) + imgID, ref, err = s.commit(ctx, s.getCreatedBy(node, addedContentSummary), !s.stepRequiresLayer(step), commitName, true, lastStage && lastInstruction) if err != nil { return "", nil, fmt.Errorf("committing final squash step %+v: %w", *step, err) } @@ -1941,7 +1941,7 @@ func (s *StageExecutor) intermediateImageExists(ctx context.Context, currNode *p // commit writes the container's contents to an image, using a passed-in tag as // the name if there is one, generating a unique ID-based one otherwise. // or commit via any custom exporter if specified. -func (s *StageExecutor) commit(ctx context.Context, createdBy string, emptyLayer bool, output string, squash bool) (string, reference.Canonical, error) { +func (s *StageExecutor) commit(ctx context.Context, createdBy string, emptyLayer bool, output string, squash, finalInstruction bool) (string, reference.Canonical, error) { ib := s.stage.Builder var imageRef types.ImageReference if output != "" { @@ -2069,6 +2069,9 @@ func (s *StageExecutor) commit(ctx context.Context, createdBy string, emptyLayer HistoryTimestamp: s.executor.timestamp, Manifest: s.executor.manifest, } + if finalInstruction { + options.ConfidentialWorkloadOptions = s.executor.confidentialWorkload + } imgID, _, manifestDigest, err := s.builder.Commit(ctx, imageRef, options) if err != nil { return "", nil, err diff --git a/internal/mkcw/archive.go b/internal/mkcw/archive.go new file mode 100644 index 00000000000..a0677e42650 --- /dev/null +++ b/internal/mkcw/archive.go @@ -0,0 +1,464 @@ +package mkcw + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/containers/luksy" + "github.com/docker/docker/pkg/ioutils" + "github.com/docker/go-units" + digest "github.com/opencontainers/go-digest" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/sirupsen/logrus" +) + +const minimumImageSize = 10 * 1024 * 1024 + +// ArchiveOptions includes optional settings for generating an archive. +type ArchiveOptions struct { + // If supplied, we'll register the workload with this server. + // Practically necessary if DiskEncryptionPassphrase is not set, in + // which case we'll generate one and throw it away after. + AttestationURL string + + // Used to measure the environment. If left unset (0, ""), defaults will be applied. + CPUs int + Memory int + + // Can be manually set. If left unset ("", false, nil), reasonable values will be used. + TempDir string + TeeType TeeType + IgnoreAttestationErrors bool + ImageSize int64 + WorkloadID string + Slop string + DiskEncryptionPassphrase string + FirmwareLibrary string + Logger *logrus.Logger +} + +type chainRetrievalError struct { + stderr string + err error +} + +func (c chainRetrievalError) Error() string { + if trimmed := strings.TrimSpace(c.stderr); trimmed != "" { + return fmt.Sprintf("retrieving SEV certificate chain: sevctl: %v: %v", strings.TrimSpace(c.stderr), c.err) + } + return fmt.Sprintf("retrieving SEV certificate chain: sevctl: %v", c.err) +} + +// Archive generates a WorkloadConfig for a specified directory and produces a +// tar archive of a container image's rootfs with the expected contents. +// The input directory will have a ".krun_config.json" file added to it while +// this function is running, but it will be removed on completion. +func Archive(path string, ociConfig *v1.Image, options ArchiveOptions) (io.ReadCloser, WorkloadConfig, error) { + const ( + teeDefaultCPUs = 2 + teeDefaultMemory = 512 + teeDefaultFilesystem = "ext4" + teeDefaultTeeType = SNP + ) + + if path == "" { + return nil, WorkloadConfig{}, fmt.Errorf("required path not specified") + } + logger := options.Logger + if logger == nil { + logger = logrus.StandardLogger() + } + + teeType := options.TeeType + if teeType == "" { + teeType = teeDefaultTeeType + } + cpus := options.CPUs + if cpus == 0 { + cpus = teeDefaultCPUs + } + memory := options.Memory + if memory == 0 { + memory = teeDefaultMemory + } + filesystem := teeDefaultFilesystem + workloadID := options.WorkloadID + if workloadID == "" { + digestInput := path + filesystem + time.Now().String() + workloadID = digest.Canonical.FromString(digestInput).Encoded() + } + workloadConfig := WorkloadConfig{ + Type: teeType, + WorkloadID: workloadID, + CPUs: cpus, + Memory: memory, + AttestationURL: options.AttestationURL, + } + + // Do things which are specific to the type of TEE we're building for. + var chainBytes []byte + var chainBytesFile string + var chainInfo fs.FileInfo + switch teeType { + default: + return nil, WorkloadConfig{}, fmt.Errorf("don't know how to generate TeeData for TEE type %q", teeType) + case SEV, SEV_NO_ES: + // If we need a certificate chain, get it. + chain, err := os.CreateTemp(options.TempDir, "chain") + if err != nil { + return nil, WorkloadConfig{}, err + } + chain.Close() + defer func() { + if err := os.Remove(chain.Name()); err != nil { + logger.Warnf("error removing temporary file %q: %v", chain.Name(), err) + } + }() + logrus.Debugf("sevctl export -f %s", chain.Name()) + cmd := exec.Command("sevctl", "export", "-f", chain.Name()) + var stdout, stderr bytes.Buffer + cmd.Stdout, cmd.Stderr = &stdout, &stderr + if err := cmd.Run(); err != nil { + if !options.IgnoreAttestationErrors { + return nil, WorkloadConfig{}, chainRetrievalError{stderr.String(), err} + } + logger.Warn(chainRetrievalError{stderr.String(), err}.Error()) + } + if chainBytes, err = os.ReadFile(chain.Name()); err != nil { + chainBytes = []byte{} + } + var teeData SevWorkloadData + if len(chainBytes) > 0 { + chainBytesFile = "sev.chain" + chainInfo, err = os.Stat(chain.Name()) + if err != nil { + return nil, WorkloadConfig{}, err + } + teeData.VendorChain = "/" + chainBytesFile + } + encodedTeeData, err := json.Marshal(teeData) + if err != nil { + return nil, WorkloadConfig{}, fmt.Errorf("encoding tee data: %w", err) + } + workloadConfig.TeeData = string(encodedTeeData) + case SNP: + teeData := SnpWorkloadData{ + Generation: "milan", + } + encodedTeeData, err := json.Marshal(teeData) + if err != nil { + return nil, WorkloadConfig{}, fmt.Errorf("encoding tee data: %w", err) + } + workloadConfig.TeeData = string(encodedTeeData) + } + + // Write part of the config blob where the krun init process will be + // looking for it. The oci2cw tool used `buildah inspect` output, but + // init is just looking for fields that have the right names in any + // object, and the image's config will have that, so let's try encoding + // it directly. + krunConfigPath := filepath.Join(path, ".krun_config.json") + krunConfigBytes, err := json.Marshal(ociConfig) + if err != nil { + return nil, WorkloadConfig{}, fmt.Errorf("creating .krun_config from image configuration: %w", err) + } + if err := ioutils.AtomicWriteFile(krunConfigPath, krunConfigBytes, 0o600); err != nil { + return nil, WorkloadConfig{}, fmt.Errorf("saving krun config: %w", err) + } + defer func() { + if err := os.Remove(krunConfigPath); err != nil { + logger.Warnf("removing krun configuration file: %v", err) + } + }() + + // Encode the workload config, in case it fails for any reason. + cleanedUpWorkloadConfig := workloadConfig + switch cleanedUpWorkloadConfig.Type { + default: + return nil, WorkloadConfig{}, fmt.Errorf("don't know how to canonicalize TEE type %q", cleanedUpWorkloadConfig.Type) + case SEV, SEV_NO_ES: + cleanedUpWorkloadConfig.Type = SEV + case SNP: + cleanedUpWorkloadConfig.Type = SNP + } + workloadConfigBytes, err := json.Marshal(cleanedUpWorkloadConfig) + if err != nil { + return nil, WorkloadConfig{}, err + } + + // Make sure we have the passphrase to use for encrypting the disk image. + diskEncryptionPassphrase := options.DiskEncryptionPassphrase + if diskEncryptionPassphrase == "" { + diskEncryptionPassphrase, err = GenerateDiskEncryptionPassphrase() + if err != nil { + return nil, WorkloadConfig{}, err + } + } + + // If we weren't told how big the image should be, get a rough estimate + // of the input data size, then add a hedge to it. + imageSize := slop(options.ImageSize, options.Slop) + if imageSize == 0 { + var sourceSize int64 + if err := filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error { + if err != nil && !errors.Is(err, os.ErrNotExist) && !errors.Is(err, os.ErrPermission) { + return err + } + info, err := d.Info() + if err != nil && !errors.Is(err, os.ErrNotExist) && !errors.Is(err, os.ErrPermission) { + return err + } + sourceSize += info.Size() + return nil + }); err != nil { + return nil, WorkloadConfig{}, err + } + imageSize = slop(sourceSize, options.Slop) + } + if imageSize%4096 != 0 { + imageSize += (4096 - (imageSize % 4096)) + } + if imageSize < minimumImageSize { + imageSize = minimumImageSize + } + + // Create a file to use as the unencrypted version of the disk image. + plain, err := os.CreateTemp(options.TempDir, "plain.img") + if err != nil { + return nil, WorkloadConfig{}, err + } + removePlain := true + defer func() { + if removePlain { + if err := os.Remove(plain.Name()); err != nil { + logger.Warnf("removing temporary file %q: %v", plain.Name(), err) + } + } + }() + + // Lengthen the plaintext disk image file. + if err := plain.Truncate(imageSize); err != nil { + plain.Close() + return nil, WorkloadConfig{}, err + } + plainInfo, err := plain.Stat() + plain.Close() + if err != nil { + return nil, WorkloadConfig{}, err + } + + // Format the disk image with the filesystem contents. + if _, stderr, err := MakeFS(path, plain.Name(), filesystem); err != nil { + if strings.TrimSpace(stderr) != "" { + return nil, WorkloadConfig{}, fmt.Errorf("%s: %w", strings.TrimSpace(stderr), err) + } + return nil, WorkloadConfig{}, err + } + + // If we're registering the workload, we can do that now. + if workloadConfig.AttestationURL != "" { + if err := SendRegistrationRequest(workloadConfig, diskEncryptionPassphrase, options.FirmwareLibrary, options.IgnoreAttestationErrors, logger); err != nil { + return nil, WorkloadConfig{}, err + } + } + + // Try to encrypt on the fly. + pipeReader, pipeWriter := io.Pipe() + removePlain = false + go func() { + var err error + defer func() { + if err := os.Remove(plain.Name()); err != nil { + logger.Warnf("removing temporary file %q: %v", plain.Name(), err) + } + if err != nil { + pipeWriter.CloseWithError(err) + } else { + pipeWriter.Close() + } + }() + plain, err := os.Open(plain.Name()) + if err != nil { + logrus.Errorf("opening unencrypted disk image %q: %v", plain.Name(), err) + return + } + defer plain.Close() + tw := tar.NewWriter(pipeWriter) + defer tw.Flush() + + // Write /entrypoint + var decompressedEntrypoint bytes.Buffer + decompressor, err := gzip.NewReader(bytes.NewReader(entrypointCompressedBytes)) + if err != nil { + logrus.Errorf("decompressing copy of entrypoint: %v", err) + return + } + defer decompressor.Close() + if _, err = io.Copy(&decompressedEntrypoint, decompressor); err != nil { + logrus.Errorf("decompressing copy of entrypoint: %v", err) + return + } + entrypointHeader, err := tar.FileInfoHeader(plainInfo, "") + if err != nil { + logrus.Errorf("building header for entrypoint: %v", err) + return + } + entrypointHeader.Name = "entrypoint" + entrypointHeader.Mode = 0o755 + entrypointHeader.Uname, entrypointHeader.Gname = "", "" + entrypointHeader.Uid, entrypointHeader.Gid = 0, 0 + entrypointHeader.Size = int64(decompressedEntrypoint.Len()) + if err = tw.WriteHeader(entrypointHeader); err != nil { + logrus.Errorf("writing header for %q: %v", entrypointHeader.Name, err) + return + } + if _, err = io.Copy(tw, &decompressedEntrypoint); err != nil { + logrus.Errorf("writing %q: %v", entrypointHeader.Name, err) + return + } + + // Write /sev.chain + if chainInfo != nil { + chainHeader, err := tar.FileInfoHeader(chainInfo, "") + if err != nil { + logrus.Errorf("building header for %q: %v", chainInfo.Name(), err) + return + } + chainHeader.Name = chainBytesFile + chainHeader.Mode = 0o600 + chainHeader.Uname, chainHeader.Gname = "", "" + chainHeader.Uid, chainHeader.Gid = 0, 0 + chainHeader.Size = int64(len(chainBytes)) + if err = tw.WriteHeader(chainHeader); err != nil { + logrus.Errorf("writing header for %q: %v", chainHeader.Name, err) + return + } + if _, err = tw.Write(chainBytes); err != nil { + logrus.Errorf("writing %q: %v", chainHeader.Name, err) + return + } + } + + // Write /krun-sev.json. + workloadConfigHeader, err := tar.FileInfoHeader(plainInfo, "") + if err != nil { + logrus.Errorf("building header for %q: %v", plainInfo.Name(), err) + return + } + workloadConfigHeader.Name = "krun-sev.json" + workloadConfigHeader.Mode = 0o600 + workloadConfigHeader.Uname, workloadConfigHeader.Gname = "", "" + workloadConfigHeader.Uid, workloadConfigHeader.Gid = 0, 0 + workloadConfigHeader.Size = int64(len(workloadConfigBytes)) + if err = tw.WriteHeader(workloadConfigHeader); err != nil { + logrus.Errorf("writing header for %q: %v", workloadConfigHeader.Name, err) + return + } + if _, err = tw.Write(workloadConfigBytes); err != nil { + logrus.Errorf("writing %q: %v", workloadConfigHeader.Name, err) + return + } + + // Write /tmp. + tmpHeader, err := tar.FileInfoHeader(plainInfo, "") + if err != nil { + logrus.Errorf("building header for %q: %v", plainInfo.Name(), err) + return + } + tmpHeader.Name = "tmp/" + tmpHeader.Typeflag = tar.TypeDir + tmpHeader.Mode = 0o1777 + tmpHeader.Uname, workloadConfigHeader.Gname = "", "" + tmpHeader.Uid, workloadConfigHeader.Gid = 0, 0 + tmpHeader.Size = 0 + if err = tw.WriteHeader(tmpHeader); err != nil { + logrus.Errorf("writing header for %q: %v", tmpHeader.Name, err) + return + } + + // Now figure out the footer that we'll append to the encrypted disk. + var footer bytes.Buffer + lengthBuffer := make([]byte, 8) + footer.Write(workloadConfigBytes) + footer.WriteString("KRUN") + binary.LittleEndian.PutUint64(lengthBuffer, uint64(len(workloadConfigBytes))) + footer.Write(lengthBuffer) + + // Start encrypting and write /disk.img. + header, encrypt, blockSize, err := luksy.EncryptV1([]string{diskEncryptionPassphrase}, "") + paddingBoundary := int64(4096) + paddingNeeded := (paddingBoundary - ((int64(len(header)) + imageSize + int64(footer.Len())) % paddingBoundary)) % paddingBoundary + diskHeader := workloadConfigHeader + diskHeader.Name = "disk.img" + diskHeader.Mode = 0o600 + diskHeader.Size = int64(len(header)) + imageSize + paddingNeeded + int64(footer.Len()) + if err = tw.WriteHeader(diskHeader); err != nil { + logrus.Errorf("writing archive header for disk.img: %v", err) + return + } + if _, err = io.Copy(tw, bytes.NewReader(header)); err != nil { + logrus.Errorf("writing encryption header for disk.img: %v", err) + return + } + encryptWrapper := luksy.EncryptWriter(encrypt, tw, blockSize) + if _, err = io.Copy(encryptWrapper, plain); err != nil { + logrus.Errorf("encrypting disk.img: %v", err) + return + } + encryptWrapper.Close() + if _, err = tw.Write(make([]byte, paddingNeeded)); err != nil { + logrus.Errorf("writing padding for disk.img: %v", err) + return + } + if _, err = io.Copy(tw, &footer); err != nil { + logrus.Errorf("writing footer for disk.img: %v", err) + return + } + tw.Close() + }() + + return pipeReader, workloadConfig, nil +} + +func slop(size int64, slop string) int64 { + if slop == "" { + return size * 5 / 4 + } + for _, factor := range strings.Split(slop, "+") { + factor = strings.TrimSpace(factor) + if factor == "" { + continue + } + if strings.HasSuffix(factor, "%") { + percentage := strings.TrimSuffix(factor, "%") + percent, err := strconv.ParseInt(percentage, 10, 8) + if err != nil { + logrus.Warnf("parsing percentage %q: %v", factor, err) + } else { + size *= (percent + 100) + size /= 100 + } + } else { + more, err := units.RAMInBytes(factor) + if err != nil { + logrus.Warnf("parsing %q as a size: %v", factor, err) + } else { + size += more + } + } + } + return size +} diff --git a/internal/mkcw/archive_test.go b/internal/mkcw/archive_test.go new file mode 100644 index 00000000000..c2e06fc3af7 --- /dev/null +++ b/internal/mkcw/archive_test.go @@ -0,0 +1,181 @@ +package mkcw + +import ( + "archive/tar" + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "net/http" + "os" + "path/filepath" + "sync" + "testing" + + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSlop(t *testing.T) { + testCases := []struct { + input int64 + slop string + output int64 + }{ + {100, "", 125}, + {100, "10%", 110}, + {100, "100%", 200}, + {100, "10GB", 10*1024*1024*1024 + 100}, + {100, "10%+10GB", 10*1024*1024*1024 + 110}, + {100, "10% + 10GB", 10*1024*1024*1024 + 110}, + } + for _, testCase := range testCases { + t.Run(testCase.slop, func(t *testing.T) { + assert.Equal(t, testCase.output, slop(testCase.input, testCase.slop)) + }) + } +} + +// dummyAttestationHandler replies with a fixed response code to requests to +// the right path, and caches passphrases indexed by workload ID +type dummyAttestationHandler struct { + t *testing.T + status int + passphrases map[string]string + passphrasesLock sync.Mutex +} + +func (d *dummyAttestationHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + var body bytes.Buffer + if req.Body != nil { + if _, err := io.Copy(&body, req.Body); err != nil { + d.t.Logf("reading request body: %v", err) + return + } + req.Body.Close() + } + if req.URL != nil && req.URL.Path == "/kbs/v0/register_workload" { + var registrationRequest RegistrationRequest + // if we can't decode the client request, bail + if err := json.Unmarshal(body.Bytes(), ®istrationRequest); err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } + // cache the passphrase + d.passphrasesLock.Lock() + if d.passphrases == nil { + d.passphrases = make(map[string]string) + } + d.passphrases[registrationRequest.WorkloadID] = registrationRequest.Passphrase + d.passphrasesLock.Unlock() + // return the predetermined status + status := d.status + if status == 0 { + status = http.StatusOK + } + rw.WriteHeader(status) + return + } + // no such handler + rw.WriteHeader(http.StatusInternalServerError) +} + +func TestArchive(t *testing.T) { + ociConfig := &v1.Image{ + Config: v1.ImageConfig{ + User: "root", + Env: []string{"PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/usr/sbin:/sbin:/usr/sbin:/sbin"}, + Cmd: []string{"/bin/bash"}, + WorkingDir: "/root", + Labels: map[string]string{ + "label_a": "b", + "label_c": "d", + }, + }, + } + for _, status := range []int{http.StatusOK, http.StatusInternalServerError} { + for _, ignoreChainRetrievalErrors := range []bool{false, true} { + for _, ignoreAttestationErrors := range []bool{false, true} { + t.Run(fmt.Sprintf("status=%d,ignoreChainRetrievalErrors=%v,ignoreAttestationErrors=%v", status, ignoreChainRetrievalErrors, ignoreAttestationErrors), func(t *testing.T) { + // listen on a system-assigned port + listener, err := net.Listen("tcp", ":0") + require.NoError(t, err) + // keep track of our listener address + addr := listener.Addr() + // serve requests on that listener + handler := &dummyAttestationHandler{t: t, status: status} + server := http.Server{ + Handler: handler, + } + go func() { + if err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { + t.Logf("serve: %v", err) + } + }() + // clean up at the end of this test + t.Cleanup(func() { assert.NoError(t, server.Close()) }) + // generate the container rootfs using a temporary empty directory + archiveOptions := ArchiveOptions{ + CPUs: 4, + Memory: 256, + TempDir: t.TempDir(), + AttestationURL: "http://" + addr.String(), + IgnoreAttestationErrors: ignoreAttestationErrors, + } + inputPath := t.TempDir() + rc, workloadConfig, err := Archive(inputPath, ociConfig, archiveOptions) + // bail now if we got an error we didn't expect + if err != nil { + if errors.As(err, &chainRetrievalError{}) { + if !ignoreChainRetrievalErrors { + return + } + } + if errors.As(err, &attestationError{}) { + if !ignoreAttestationErrors { + require.NoError(t, err) + } + } + return + } + if err == nil { + defer rc.Close() + } + // read each archive entry's contents into a map + contents := make(map[string][]byte) + tr := tar.NewReader(rc) + hdr, err := tr.Next() + for hdr != nil { + contents[hdr.Name], err = io.ReadAll(tr) + require.NoError(t, err) + hdr, err = tr.Next() + } + if err != nil { + require.ErrorIs(t, err, io.EOF) + } + // check that krun-sev.json is a JSON-encoded copy of the workload config + var writtenWorkloadConfig WorkloadConfig + err = json.Unmarshal(contents["krun-sev.json"], &writtenWorkloadConfig) + require.NoError(t, err) + assert.Equal(t, workloadConfig, writtenWorkloadConfig) + // save the disk image to a file + encryptedFile := filepath.Join(t.TempDir(), "encrypted.img") + err = os.WriteFile(encryptedFile, contents["disk.img"], 0o600) + require.NoError(t, err) + // check that we have a configuration footer in there + _, err = ReadWorkloadConfigFromImage(encryptedFile) + require.NoError(t, err) + // check that the attestation server got the encryption passphrase + handler.passphrasesLock.Lock() + passphrase := handler.passphrases[workloadConfig.WorkloadID] + handler.passphrasesLock.Unlock() + err = CheckLUKSPassphrase(encryptedFile, passphrase) + require.NoError(t, err) + }) + } + } + } +} diff --git a/internal/mkcw/attest.go b/internal/mkcw/attest.go new file mode 100644 index 00000000000..91362d37e2d --- /dev/null +++ b/internal/mkcw/attest.go @@ -0,0 +1,250 @@ +package mkcw + +import ( + "bufio" + "bytes" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + + "github.com/containers/buildah/internal/mkcw/types" + "github.com/sirupsen/logrus" +) + +type ( + RegistrationRequest = types.RegistrationRequest + TeeConfig = types.TeeConfig + TeeConfigFlags = types.TeeConfigFlags + TeeConfigMinFW = types.TeeConfigMinFW +) + +type measurementError struct { + err error +} + +func (m measurementError) Error() string { + return fmt.Sprintf("generating measurement for attestation: %v", m.err) +} + +type attestationError struct { + err error +} + +func (a attestationError) Error() string { + return fmt.Sprintf("registering workload: %v", a.err) +} + +type httpError struct { + statusCode int +} + +func (h httpError) Error() string { + if statusText := http.StatusText(h.statusCode); statusText != "" { + return fmt.Sprintf("received server status %d (%q)", h.statusCode, statusText) + } + return fmt.Sprintf("received server status %d", h.statusCode) +} + +// SendRegistrationRequest registers a workload with the specified decryption +// passphrase with the service whose location is part of the WorkloadConfig. +func SendRegistrationRequest(workloadConfig WorkloadConfig, diskEncryptionPassphrase, firmwareLibrary string, ignoreAttestationErrors bool, logger *logrus.Logger) error { + if workloadConfig.AttestationURL == "" { + return errors.New("attestation URL not provided") + } + + // Measure the execution environment. + measurement, err := GenerateMeasurement(workloadConfig, firmwareLibrary) + if err != nil { + if !ignoreAttestationErrors { + return &measurementError{err} + } + logger.Warnf("generating measurement for attestation: %v", err) + } + + // Build the workload registration (attestation) request body. + var teeConfigBytes []byte + switch workloadConfig.Type { + case SEV, SEV_NO_ES, SNP: + var cbits types.TeeConfigFlagBits + switch workloadConfig.Type { + case SEV: + cbits = types.SEV_CONFIG_NO_DEBUG | + types.SEV_CONFIG_NO_KEY_SHARING | + types.SEV_CONFIG_ENCRYPTED_STATE | + types.SEV_CONFIG_NO_SEND | + types.SEV_CONFIG_DOMAIN | + types.SEV_CONFIG_SEV + case SEV_NO_ES: + cbits = types.SEV_CONFIG_NO_DEBUG | + types.SEV_CONFIG_NO_KEY_SHARING | + types.SEV_CONFIG_NO_SEND | + types.SEV_CONFIG_DOMAIN | + types.SEV_CONFIG_SEV + case SNP: + cbits = types.SNP_CONFIG_SMT | + types.SNP_CONFIG_MANDATORY | + types.SNP_CONFIG_MIGRATE_MA | + types.SNP_CONFIG_DEBUG + default: + panic("internal error") // shouldn't happen + } + teeConfig := TeeConfig{ + Flags: TeeConfigFlags{ + Bits: cbits, + }, + MinFW: TeeConfigMinFW{ + Major: 0, + Minor: 0, + }, + } + teeConfigBytes, err = json.Marshal(teeConfig) + if err != nil { + return err + } + default: + return fmt.Errorf("don't know how to generate tee_config for %q TEEs", workloadConfig.Type) + } + + registrationRequest := RegistrationRequest{ + WorkloadID: workloadConfig.WorkloadID, + LaunchMeasurement: measurement, + TeeConfig: string(teeConfigBytes), + Passphrase: diskEncryptionPassphrase, + } + registrationRequestBytes, err := json.Marshal(registrationRequest) + if err != nil { + return err + } + + // Register the workload. + parsedURL, err := url.Parse(workloadConfig.AttestationURL) + if err != nil { + return err + } + parsedURL.Path = path.Join(parsedURL.Path, "/kbs/v0/register_workload") + if err != nil { + return err + } + url := parsedURL.String() + requestContentType := "application/json" + requestBody := bytes.NewReader(registrationRequestBytes) + defer http.DefaultClient.CloseIdleConnections() + resp, err := http.Post(url, requestContentType, requestBody) + if resp != nil { + if resp.Body != nil { + resp.Body.Close() + } + switch resp.StatusCode { + default: + if !ignoreAttestationErrors { + return &attestationError{&httpError{resp.StatusCode}} + } + logger.Warn(attestationError{&httpError{resp.StatusCode}}.Error()) + case http.StatusOK, http.StatusAccepted: + // great! + } + } + if err != nil { + if !ignoreAttestationErrors { + return &attestationError{err} + } + logger.Warn(attestationError{err}.Error()) + } + return nil +} + +// GenerateMeasurement generates the runtime measurement using the CPU count, +// memory size, and the firmware shared library, whatever it's called, wherever +// it is. +// If firmwareLibrary is a path, it will be the only one checked. +// If firmwareLibrary is a filename, it will be checked for in a hard-coded set +// of directories. +// If firmwareLibrary is empty, both the filename and the directory it is in +// will be taken from a hard-coded set of candidates. +func GenerateMeasurement(workloadConfig WorkloadConfig, firmwareLibrary string) (string, error) { + cpuString := fmt.Sprintf("%d", workloadConfig.CPUs) + memoryString := fmt.Sprintf("%d", workloadConfig.Memory) + var prefix string + switch workloadConfig.Type { + case SEV: + prefix = "SEV-ES" + case SEV_NO_ES: + prefix = "SEV" + case SNP: + prefix = "SNP" + default: + return "", fmt.Errorf("don't know which measurement to use for TEE type %q", workloadConfig.Type) + } + + sharedLibraryDirs := []string{ + "/usr/local/lib64", + "/usr/local/lib", + "/lib64", + "/lib", + "/usr/lib64", + "/usr/lib", + } + if llp, ok := os.LookupEnv("LD_LIBRARY_PATH"); ok { + sharedLibraryDirs = append(sharedLibraryDirs, strings.Split(llp, ":")...) + } + libkrunfwNames := []string{ + "libkrunfw-sev.so.4", + "libkrunfw-sev.so.3", + "libkrunfw-sev.so", + } + var pathsToCheck []string + if firmwareLibrary == "" { + for _, sharedLibraryDir := range sharedLibraryDirs { + if sharedLibraryDir == "" { + continue + } + for _, libkrunfw := range libkrunfwNames { + candidate := filepath.Join(sharedLibraryDir, libkrunfw) + pathsToCheck = append(pathsToCheck, candidate) + } + } + } else { + if filepath.IsAbs(firmwareLibrary) { + pathsToCheck = append(pathsToCheck, firmwareLibrary) + } else { + for _, sharedLibraryDir := range sharedLibraryDirs { + if sharedLibraryDir == "" { + continue + } + candidate := filepath.Join(sharedLibraryDir, firmwareLibrary) + pathsToCheck = append(pathsToCheck, candidate) + } + } + } + for _, candidate := range pathsToCheck { + if _, err := os.Lstat(candidate); err == nil { + var stdout, stderr bytes.Buffer + logrus.Debugf("krunfw_measurement -c %s -m %s %s", cpuString, memoryString, candidate) + cmd := exec.Command("krunfw_measurement", "-c", cpuString, "-m", memoryString, candidate) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + if stderr.Len() > 0 { + err = fmt.Errorf("krunfw_measurement: %s: %w", strings.TrimSpace(stderr.String()), err) + } + return "", err + } + scanner := bufio.NewScanner(&stdout) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, prefix+":") { + return strings.TrimSpace(strings.TrimPrefix(line, prefix+":")), nil + } + } + return "", fmt.Errorf("generating measurement: no line starting with %q found in output from krunfw_measurement", prefix+":") + } + } + return "", fmt.Errorf("generating measurement: none of %v found: %w", pathsToCheck, os.ErrNotExist) +} diff --git a/internal/mkcw/embed/entrypoint b/internal/mkcw/embed/entrypoint new file mode 100755 index 00000000000..526c500369e Binary files /dev/null and b/internal/mkcw/embed/entrypoint differ diff --git a/internal/mkcw/embed/entrypoint.gz b/internal/mkcw/embed/entrypoint.gz new file mode 100755 index 00000000000..7f23fa15cad Binary files /dev/null and b/internal/mkcw/embed/entrypoint.gz differ diff --git a/internal/mkcw/embed/entrypoint.s b/internal/mkcw/embed/entrypoint.s new file mode 100644 index 00000000000..0e4429cfb42 --- /dev/null +++ b/internal/mkcw/embed/entrypoint.s @@ -0,0 +1,16 @@ + .section .rodata.1,"aMS",@progbits,1 +msg: + .string "This image is designed to be run as a confidential workload using libkrun.\n" + .section .text._start,"ax",@progbits + .globl _start + .type _start,@function +_start: + movq $1, %rax # write + movq $2, %rdi # fd=stderr_fileno + movq $msg, %rsi # message + movq $75, %rdx # length + syscall + movq $60, %rax # exit + movq $1, %rdi # status=1 + syscall + .section .note.GNU-stack,"",@progbits diff --git a/internal/mkcw/entrypoint.go b/internal/mkcw/entrypoint.go new file mode 100644 index 00000000000..d7203216853 --- /dev/null +++ b/internal/mkcw/entrypoint.go @@ -0,0 +1,6 @@ +package mkcw + +import _ "embed" + +//go:embed "embed/entrypoint.gz" +var entrypointCompressedBytes []byte diff --git a/internal/mkcw/luks.go b/internal/mkcw/luks.go new file mode 100644 index 00000000000..0d795e6a095 --- /dev/null +++ b/internal/mkcw/luks.go @@ -0,0 +1,51 @@ +package mkcw + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "os" + + "github.com/containers/luksy" +) + +// CheckLUKSPassphrase checks that the specified LUKS-encrypted file can be +// decrypted using the specified passphrase. +func CheckLUKSPassphrase(path, decryptionPassphrase string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + v1header, v2headerA, v2headerB, v2json, err := luksy.ReadHeaders(f, luksy.ReadHeaderOptions{}) + if err != nil { + return err + } + if v1header != nil { + _, _, _, _, err = v1header.Decrypt(decryptionPassphrase, f) + return err + } + if v2headerA == nil && v2headerB == nil { + return fmt.Errorf("no LUKS headers read from %q", path) + } + if v2headerA != nil { + if _, _, _, _, err = v2headerA.Decrypt(decryptionPassphrase, f, *v2json); err != nil { + return err + } + } + if v2headerB != nil { + if _, _, _, _, err = v2headerB.Decrypt(decryptionPassphrase, f, *v2json); err != nil { + return err + } + } + return nil +} + +// GenerateDiskEncryptionPassphrase generates a random disk encryption password +func GenerateDiskEncryptionPassphrase() (string, error) { + randomizedBytes := make([]byte, 32) + if _, err := rand.Read(randomizedBytes); err != nil { + return "", err + } + return hex.EncodeToString(randomizedBytes), nil +} diff --git a/internal/mkcw/luks_test.go b/internal/mkcw/luks_test.go new file mode 100644 index 00000000000..3df723ffb86 --- /dev/null +++ b/internal/mkcw/luks_test.go @@ -0,0 +1,66 @@ +package mkcw + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/containers/luksy" + "github.com/stretchr/testify/require" +) + +func TestCheckLUKSPassphrase(t *testing.T) { + passphrase, err := GenerateDiskEncryptionPassphrase() + require.NoError(t, err) + secondPassphrase, err := GenerateDiskEncryptionPassphrase() + require.NoError(t, err) + + t.Run("v1", func(t *testing.T) { + header, encrypter, blockSize, err := luksy.EncryptV1([]string{secondPassphrase, passphrase}, "") + require.NoError(t, err) + f, err := os.Create(filepath.Join(t.TempDir(), "v1")) + require.NoError(t, err) + n, err := f.Write(header) + require.NoError(t, err) + require.Equal(t, len(header), n) + wrapper := luksy.EncryptWriter(encrypter, f, blockSize) + _, err = wrapper.Write(make([]byte, blockSize*10)) + require.NoError(t, err) + wrapper.Close() + f.Close() + + err = CheckLUKSPassphrase(f.Name(), passphrase) + require.NoError(t, err) + err = CheckLUKSPassphrase(f.Name(), secondPassphrase) + require.NoError(t, err) + err = CheckLUKSPassphrase(f.Name(), "nope, this is not a correct passphrase") + require.Error(t, err) + }) + + t.Run("v2", func(t *testing.T) { + for _, sectorSize := range []int{512, 1024, 2048, 4096} { + t.Run(fmt.Sprintf("sectorSize=%d", sectorSize), func(t *testing.T) { + header, encrypter, blockSize, err := luksy.EncryptV2([]string{secondPassphrase, passphrase}, "", sectorSize) + require.NoError(t, err) + f, err := os.Create(filepath.Join(t.TempDir(), "v2")) + require.NoError(t, err) + n, err := f.Write(header) + require.NoError(t, err) + require.Equal(t, len(header), n) + wrapper := luksy.EncryptWriter(encrypter, f, blockSize) + _, err = wrapper.Write(make([]byte, blockSize*10)) + require.NoError(t, err) + wrapper.Close() + f.Close() + + err = CheckLUKSPassphrase(f.Name(), passphrase) + require.NoError(t, err) + err = CheckLUKSPassphrase(f.Name(), secondPassphrase) + require.NoError(t, err) + err = CheckLUKSPassphrase(f.Name(), "nope, this is not one of the correct passphrases") + require.Error(t, err) + }) + } + }) +} diff --git a/internal/mkcw/makefs.go b/internal/mkcw/makefs.go new file mode 100644 index 00000000000..308f2a9d08a --- /dev/null +++ b/internal/mkcw/makefs.go @@ -0,0 +1,38 @@ +package mkcw + +import ( + "fmt" + "os/exec" + "strings" + + "github.com/sirupsen/logrus" +) + +// MakeFS formats the imageFile as a filesystem of the specified type, +// populating it with the contents of the directory at sourcePath. +// Recognized filesystem types are "ext2", "ext3", "ext4", and "btrfs". +// Note that krun's init is currently hard-wired to assume "ext4". +// Returns the stdout, stderr, and any error returned by the mkfs command. +func MakeFS(sourcePath, imageFile, filesystem string) (string, string, error) { + var stdout, stderr strings.Builder + // N.B. mkfs.xfs can accept a protofile via its -p option, but the + // protofile format doesn't allow us to supply timestamp information or + // specify that files are hard linked + switch filesystem { + case "ext2", "ext3", "ext4": + logrus.Debugf("mkfs -t %s --rootdir %q %q", filesystem, sourcePath, imageFile) + cmd := exec.Command("mkfs", "-t", filesystem, "-d", sourcePath, imageFile) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + return stdout.String(), stderr.String(), err + case "btrfs": + logrus.Debugf("mkfs -t %s --rootdir %q %q", filesystem, sourcePath, imageFile) + cmd := exec.Command("mkfs", "-t", filesystem, "--rootdir", sourcePath, imageFile) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + return stdout.String(), stderr.String(), err + } + return "", "", fmt.Errorf("don't know how to make a %q filesystem with contents", filesystem) +} diff --git a/internal/mkcw/types/attest.go b/internal/mkcw/types/attest.go new file mode 100644 index 00000000000..276c7f0c0ac --- /dev/null +++ b/internal/mkcw/types/attest.go @@ -0,0 +1,47 @@ +package types + +// RegistrationRequest is the body of the request which we use for registering +// this confidential workload with the attestation server. +// https://github.com/virtee/reference-kbs/blob/10b2a4c0f8caf78a077210b172863bbae54f66aa/src/main.rs#L83 +type RegistrationRequest struct { + WorkloadID string `json:"workload_id"` + LaunchMeasurement string `json:"launch_measurement"` + Passphrase string `json:"passphrase"` + TeeConfig string `json:"tee_config"` // JSON-encoded teeConfig? or specific to the type of TEE? +} + +// TeeConfig contains information about a trusted execution environment. +type TeeConfig struct { + Flags TeeConfigFlags `json:"flags"` // runtime requirement bits + MinFW TeeConfigMinFW `json:"minfw"` // minimum platform firmware version +} + +// TeeConfigFlags is a bit field containing policy flags specific to the environment. +// https://github.com/virtee/sev/blob/d3e40917fd8531c69f47c2498e9667fe8a5303aa/src/launch/sev.rs#L172 +// https://github.com/virtee/sev/blob/d3e40917fd8531c69f47c2498e9667fe8a5303aa/src/launch/snp.rs#L114 +type TeeConfigFlags struct { + Bits TeeConfigFlagBits `json:"bits"` +} + +// TeeConfigFlagBits are bits representing run-time expectations. +type TeeConfigFlagBits int + +const ( + SEV_CONFIG_NO_DEBUG TeeConfigFlagBits = 0b00000001 //revive:disable-line:var-naming no debugging of guests + SEV_CONFIG_NO_KEY_SHARING TeeConfigFlagBits = 0b00000010 //revive:disable-line:var-naming no sharing keys between guests + SEV_CONFIG_ENCRYPTED_STATE TeeConfigFlagBits = 0b00000100 //revive:disable-line:var-naming requires SEV-ES + SEV_CONFIG_NO_SEND TeeConfigFlagBits = 0b00001000 //revive:disable-line:var-naming no transferring the guest to another platform + SEV_CONFIG_DOMAIN TeeConfigFlagBits = 0b00010000 //revive:disable-line:var-naming no transferring the guest out of the domain (?) + SEV_CONFIG_SEV TeeConfigFlagBits = 0b00100000 //revive:disable-line:var-naming no transferring the guest to non-SEV platforms + SNP_CONFIG_SMT TeeConfigFlagBits = 0b00000001 //revive:disable-line:var-naming SMT is enabled on the host machine + SNP_CONFIG_MANDATORY TeeConfigFlagBits = 0b00000010 //revive:disable-line:var-naming reserved bit which should always be set + SNP_CONFIG_MIGRATE_MA TeeConfigFlagBits = 0b00000100 //revive:disable-line:var-naming allowed to use a migration agent + SNP_CONFIG_DEBUG TeeConfigFlagBits = 0b00001000 //revive:disable-line:var-naming allow debugging +) + +// TeeConfigFlagMinFW corresponds to a minimum version of the kernel+initrd +// combination that should be booted. +type TeeConfigMinFW struct { + Major int `json:"major"` + Minor int `json:"minor"` +} diff --git a/internal/mkcw/types/workload.go b/internal/mkcw/types/workload.go new file mode 100644 index 00000000000..249683b0e75 --- /dev/null +++ b/internal/mkcw/types/workload.go @@ -0,0 +1,34 @@ +package types + +import "github.com/containers/buildah/define" + +// WorkloadConfig is the data type which is encoded and stored in /krun-sev.json in a container +// image, and included directly in the disk image. +// https://github.com/containers/libkrun/blob/57c59dc5359bdeeb8260b3493e9f63d3708f9ab9/src/vmm/src/resources.rs#L57 +type WorkloadConfig struct { + Type define.TeeType `json:"tee"` + TeeData string `json:"tee_data"` // Type == SEV: JSON-encoded SevWorkloadData, SNP: JSON-encoded SnpWorkloadData, others? + WorkloadID string `json:"workload_id"` + CPUs int `json:"cpus"` + Memory int `json:"ram_mib"` + AttestationURL string `json:"attestation_url"` +} + +// SevWorkloadData contains the path to the SEV certificate chain and optionally, +// the attestation server's public key(?) +// https://github.com/containers/libkrun/blob/d31747aa92cf83df2abaeb87e2a83311c135d003/src/vmm/src/linux/tee/amdsev.rs#L222 +type SevWorkloadData struct { + VendorChain string `json:"vendor_chain"` + AttestationServerPubkey string `json:"attestation_server_pubkey"` +} + +// SnpWorkloadData contains the required CPU generation name. +// https://github.com/virtee/oci2cw/blob/1502d5be33c2fa82d49aaa95781bbab2aa932781/examples/tee-config-snp.json +type SnpWorkloadData struct { + Generation string `json:"gen"` // "milan" (naples=1, rome=2, milan=3, genoa/bergamo=4) +} + +const ( + // SEV_NO_ES is a known trusted execution environment type: AMD-SEV (secure encrypted virtualization without encrypted state, requires epyc 1000 "naples") + SEV_NO_ES define.TeeType = "sev_no_es" //revive:disable-line:var-naming +) diff --git a/internal/mkcw/workload.go b/internal/mkcw/workload.go new file mode 100644 index 00000000000..ca97daaf942 --- /dev/null +++ b/internal/mkcw/workload.go @@ -0,0 +1,223 @@ +package mkcw + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "io" + "os" + + "github.com/containers/buildah/define" + "github.com/containers/buildah/internal/mkcw/types" +) + +type ( + // WorkloadConfig is the data type which is encoded and stored in an image. + WorkloadConfig = types.WorkloadConfig + // SevWorkloadData is the type of data in WorkloadConfig.TeeData when the type is SEV. + SevWorkloadData = types.SevWorkloadData + // SnpWorkloadData is the type of data in WorkloadConfig.TeeData when the type is SNP. + SnpWorkloadData = types.SnpWorkloadData + // TeeType is one of the known types of trusted execution environments for which we + // can generate suitable image contents. + TeeType = define.TeeType +) + +const ( + maxWorkloadConfigSize = 1024 * 1024 + preferredPaddingBoundary = 4096 + // SEV is a known trusted execution environment type: AMD-SEV + SEV = define.SEV + // SEV_NO_ES is a known trusted execution environment type: AMD-SEV without encrypted state + SEV_NO_ES = types.SEV_NO_ES //revive:disable-line:var-naming + // SNP is a known trusted execution environment type: AMD-SNP + SNP = define.SNP + // krun looks for its configuration JSON directly in a disk image if the last twelve bytes + // of the disk image are this magic value followed by a little-endian 64-bit + // length-of-the-configuration + krunMagic = "KRUN" +) + +// ReadWorkloadConfigFromImage reads the workload configuration from the +// specified disk image file +func ReadWorkloadConfigFromImage(path string) (WorkloadConfig, error) { + // Read the last 12 bytes, which should be "KRUN" followed by a 64-bit + // little-endian length. The (length) bytes immediately preceding + // these hold the JSON-encoded workloadConfig. + var wc WorkloadConfig + f, err := os.Open(path) + if err != nil { + return wc, err + } + defer f.Close() + + // Read those last 12 bytes. + finalTwelve := make([]byte, 12) + if _, err = f.Seek(-12, io.SeekEnd); err != nil { + return wc, fmt.Errorf("checking for workload config signature: %w", err) + } + if n, err := f.Read(finalTwelve); err != nil || n != len(finalTwelve) { + if err != nil && !errors.Is(err, io.EOF) { + return wc, fmt.Errorf("reading workload config signature (%d bytes read): %w", n, err) + } + if n != len(finalTwelve) { + return wc, fmt.Errorf("short read (expected 12 bytes at the end of %q, got %d)", path, n) + } + } + if magic := string(finalTwelve[0:4]); magic != "KRUN" { + return wc, fmt.Errorf("expected magic string KRUN in %q, found %q)", path, magic) + } + length := binary.LittleEndian.Uint64(finalTwelve[4:]) + if length > maxWorkloadConfigSize { + return wc, fmt.Errorf("workload config in %q is %d bytes long, which seems unreasonable (max allowed %d)", path, length, maxWorkloadConfigSize) + } + + // Read and decode the config. + configBytes := make([]byte, length) + if _, err = f.Seek(-(int64(length) + 12), io.SeekEnd); err != nil { + return wc, fmt.Errorf("looking for workload config from disk image: %w", err) + } + if n, err := f.Read(configBytes); err != nil || n != len(configBytes) { + if err != nil { + return wc, fmt.Errorf("reading workload config from disk image: %w", err) + } + return wc, fmt.Errorf("short read (expected %d bytes near the end of %q, got %d)", len(configBytes), path, n) + } + err = json.Unmarshal(configBytes, &wc) + if err != nil { + err = fmt.Errorf("unmarshaling configuration %q: %w", string(configBytes), err) + } + return wc, err +} + +// WriteWorkloadConfigToImage writes the workload configuration to the +// specified disk image file, overwriting a previous configuration if it's +// asked to and it finds one +func WriteWorkloadConfigToImage(imageFile *os.File, workloadConfigBytes []byte, overwrite bool) error { + // Read those last 12 bytes to check if there's a configuration there already, which we should overwrite. + var overwriteOffset int64 + if overwrite { + finalTwelve := make([]byte, 12) + if _, err := imageFile.Seek(-12, io.SeekEnd); err != nil { + return fmt.Errorf("checking for workload config signature: %w", err) + } + if n, err := imageFile.Read(finalTwelve); err != nil || n != len(finalTwelve) { + if err != nil && !errors.Is(err, io.EOF) { + return fmt.Errorf("reading workload config signature (%d bytes read): %w", n, err) + } + if n != len(finalTwelve) { + return fmt.Errorf("short read (expected 12 bytes at the end of %q, got %d)", imageFile.Name(), n) + } + } + if magic := string(finalTwelve[0:4]); magic == "KRUN" { + length := binary.LittleEndian.Uint64(finalTwelve[4:]) + if length < maxWorkloadConfigSize { + overwriteOffset = int64(length + 12) + } + } + } + // If we found a configuration in the file, try to figure out how much padding was used. + paddingSize := int64(preferredPaddingBoundary) + if overwriteOffset != 0 { + st, err := imageFile.Stat() + if err != nil { + return err + } + for _, possiblePaddingLength := range []int64{0x100000, 0x10000, 0x1000, 0x200, 0x100} { + if overwriteOffset > possiblePaddingLength { + continue + } + if st.Size()%possiblePaddingLength != 0 { + continue + } + if _, err := imageFile.Seek(-possiblePaddingLength, io.SeekEnd); err != nil { + return fmt.Errorf("checking size of padding at end of file: %w", err) + } + buf := make([]byte, possiblePaddingLength) + n, err := imageFile.Read(buf) + if err != nil { + return fmt.Errorf("reading possible padding at end of file: %w", err) + } + if n != len(buf) { + return fmt.Errorf("short read checking size of padding at end of file: %d != %d", n, len(buf)) + } + if bytes.Equal(buf[:possiblePaddingLength-overwriteOffset], make([]byte, possiblePaddingLength-overwriteOffset)) { + // everything up to the configuration was zero bytes, so it was padding + overwriteOffset = possiblePaddingLength + paddingSize = possiblePaddingLength + break + } + } + } + + // Append the krun configuration to a new buffer. + var formatted bytes.Buffer + nWritten, err := formatted.Write(workloadConfigBytes) + if err != nil { + return fmt.Errorf("building workload config: %w", err) + } + if nWritten != len(workloadConfigBytes) { + return fmt.Errorf("short write appending configuration to buffer: %d != %d", nWritten, len(workloadConfigBytes)) + } + // Append the magic string to the buffer. + nWritten, err = formatted.WriteString(krunMagic) + if err != nil { + return fmt.Errorf("building workload config signature: %w", err) + } + if nWritten != len(krunMagic) { + return fmt.Errorf("short write appending krun magic to buffer: %d != %d", nWritten, len(krunMagic)) + } + // Append the 64-bit little-endian length of the workload configuration to the buffer. + workloadConfigLengthBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(workloadConfigLengthBytes, uint64(len(workloadConfigBytes))) + nWritten, err = formatted.Write(workloadConfigLengthBytes) + if err != nil { + return fmt.Errorf("building workload config signature size: %w", err) + } + if nWritten != len(workloadConfigLengthBytes) { + return fmt.Errorf("short write appending configuration length to buffer: %d != %d", nWritten, len(workloadConfigLengthBytes)) + } + + // Build a copy of that data, with padding preceding it. + var padded bytes.Buffer + if int64(formatted.Len())%paddingSize != 0 { + extra := paddingSize - (int64(formatted.Len()) % paddingSize) + nWritten, err := padded.Write(make([]byte, extra)) + if err != nil { + return fmt.Errorf("buffering padding: %w", err) + } + if int64(nWritten) != extra { + return fmt.Errorf("short write buffering padding for disk image: %d != %d", nWritten, extra) + } + } + extra := int64(formatted.Len()) + nWritten, err = padded.Write(formatted.Bytes()) + if err != nil { + return fmt.Errorf("buffering workload config: %w", err) + } + if int64(nWritten) != extra { + return fmt.Errorf("short write buffering workload config: %d != %d", nWritten, extra) + } + + // Write the buffer to the file, starting with padding. + if _, err = imageFile.Seek(-overwriteOffset, io.SeekEnd); err != nil { + return fmt.Errorf("preparing to write workload config: %w", err) + } + nWritten, err = imageFile.Write(padded.Bytes()) + if err != nil { + return fmt.Errorf("writing workload config: %w", err) + } + if nWritten != padded.Len() { + return fmt.Errorf("short write writing configuration to disk image: %d != %d", nWritten, padded.Len()) + } + offset, err := imageFile.Seek(0, io.SeekCurrent) + if err != nil { + return fmt.Errorf("preparing mark end of disk image: %w", err) + } + if err = imageFile.Truncate(offset); err != nil { + return fmt.Errorf("marking end of disk image: %w", err) + } + return nil +} diff --git a/internal/mkcw/workload_test.go b/internal/mkcw/workload_test.go new file mode 100644 index 00000000000..2de766f97a0 --- /dev/null +++ b/internal/mkcw/workload_test.go @@ -0,0 +1,62 @@ +package mkcw + +import ( + "crypto/rand" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestReadWriteWorkloadConfig(t *testing.T) { + // Create a temporary file to stand in for a disk image. + temp := filepath.Join(t.TempDir(), "disk.img") + f, err := os.OpenFile(temp, os.O_CREATE|os.O_RDWR, 0o600) + require.NoError(t, err) + err = f.Truncate(0x1000000) + require.NoError(t, err) + defer f.Close() + + // Generate a random "encoded workload config". + workloadConfig := make([]byte, 0x100) + n, err := rand.Read(workloadConfig) + require.NoError(t, err) + require.Equal(t, len(workloadConfig), n) + + // Read the size of our temporary file. + st, err := f.Stat() + require.NoError(t, err) + originalSize := st.Size() + + // Should get an error, since there's no workloadConfig in there to read. + _, err = ReadWorkloadConfigFromImage(f.Name()) + require.Error(t, err) + + // File should grow, even though we looked for an old config to overwrite. + err = WriteWorkloadConfigToImage(f, workloadConfig, true) + require.NoError(t, err) + st, err = f.Stat() + require.NoError(t, err) + require.Greater(t, st.Size(), originalSize) + originalSize = st.Size() + + // File shouldn't grow, even overwriting the config with a slightly larger one. + err = WriteWorkloadConfigToImage(f, append([]byte("slightly longer"), workloadConfig...), true) + require.NoError(t, err) + st, err = f.Stat() + require.NoError(t, err) + require.Equal(t, originalSize, st.Size()) + originalSize = st.Size() + + // File should grow if we're not trying to replace an old one config with a new one. + err = WriteWorkloadConfigToImage(f, []byte("{\"comment\":\"quite a bit shorter\"}"), false) + require.NoError(t, err) + st, err = f.Stat() + require.NoError(t, err) + require.Greater(t, st.Size(), originalSize) + + // Should read successfully. + _, err = ReadWorkloadConfigFromImage(f.Name()) + require.NoError(t, err) +} diff --git a/pkg/cli/build.go b/pkg/cli/build.go index f6b25795153..6a0948e854c 100644 --- a/pkg/cli/build.go +++ b/pkg/cli/build.go @@ -296,6 +296,13 @@ func GenBuildOptions(c *cobra.Command, inputArgs []string, iopts BuildOptions) ( iopts.Quiet = true } } + var confidentialWorkloadOptions define.ConfidentialWorkloadOptions + if c.Flag("cw").Changed { + confidentialWorkloadOptions, err = parse.GetConfidentialWorkloadOptions(iopts.CWOptions) + if err != nil { + return options, nil, nil, err + } + } var cacheTo []reference.Named var cacheFrom []reference.Named cacheTo = nil @@ -364,6 +371,7 @@ func GenBuildOptions(c *cobra.Command, inputArgs []string, iopts BuildOptions) ( CacheTTL: cacheTTL, CNIConfigDir: iopts.CNIConfigDir, CNIPluginPath: iopts.CNIPlugInPath, + ConfidentialWorkload: confidentialWorkloadOptions, CPPFlags: iopts.CPPFlags, CommonBuildOpts: commonOpts, Compression: compression, diff --git a/pkg/cli/common.go b/pkg/cli/common.go index cb05d02e8aa..6ff50d99fe7 100644 --- a/pkg/cli/common.go +++ b/pkg/cli/common.go @@ -107,6 +107,7 @@ type BudResults struct { Envs []string OSFeatures []string OSVersion string + CWOptions string } // FromAndBugResults represents the results for common flags @@ -217,6 +218,7 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet { fs.BoolVar(&flags.Compress, "compress", false, "this is a legacy option, which has no effect on the image") fs.StringArrayVar(&flags.CPPFlags, "cpp-flag", []string{}, "set additional flag to pass to C preprocessor (cpp)") fs.StringVar(&flags.Creds, "creds", "", "use `[username[:password]]` for accessing the registry") + fs.StringVarP(&flags.CWOptions, "cw", "", "", "confidential workload `options`") fs.BoolVarP(&flags.DisableCompression, "disable-compression", "D", true, "don't compress layers by default") fs.BoolVar(&flags.DisableContentTrust, "disable-content-trust", false, "this is a Docker specific option and is a NOOP") fs.StringArrayVar(&flags.Envs, "env", []string{}, "set environment variable for the image") @@ -299,6 +301,7 @@ func GetBudFlagsCompletions() commonComp.FlagCompletions { flagCompletion["cert-dir"] = commonComp.AutocompleteDefault flagCompletion["cpp-flag"] = commonComp.AutocompleteNone flagCompletion["creds"] = commonComp.AutocompleteNone + flagCompletion["cw"] = commonComp.AutocompleteNone flagCompletion["env"] = commonComp.AutocompleteNone flagCompletion["file"] = commonComp.AutocompleteDefault flagCompletion["format"] = commonComp.AutocompleteNone diff --git a/pkg/parse/parse.go b/pkg/parse/parse.go index 775f4a9900c..c106949517f 100644 --- a/pkg/parse/parse.go +++ b/pkg/parse/parse.go @@ -16,6 +16,7 @@ import ( "github.com/containerd/containerd/platforms" "github.com/containers/buildah/define" + mkcwtypes "github.com/containers/buildah/internal/mkcw/types" internalParse "github.com/containers/buildah/internal/parse" internalUtil "github.com/containers/buildah/internal/util" "github.com/containers/buildah/pkg/sshagent" @@ -640,6 +641,76 @@ func GetBuildOutput(buildOutput string) (define.BuildOutputOption, error) { return define.BuildOutputOption{Path: path, IsDir: isDir, IsStdout: isStdout}, nil } +// GetConfidentialWorkloadOptions parses a confidential workload settings +// argument, which controls both whether or not we produce an image that +// expects to be run using krun, and how we handle things like encrypting +// the disk image that the container image will contain. +func GetConfidentialWorkloadOptions(arg string) (define.ConfidentialWorkloadOptions, error) { + options := define.ConfidentialWorkloadOptions{ + TempDir: GetTempDir(), + } + defaults := options + for _, option := range strings.Split(arg, ",") { + var err error + switch { + case strings.HasPrefix(option, "type="): + options.TeeType = define.TeeType(strings.ToLower(strings.TrimPrefix(option, "type="))) + switch options.TeeType { + case define.SEV, define.SNP, mkcwtypes.SEV_NO_ES: + default: + return options, fmt.Errorf("parsing type= value %q: unrecognized value", options.TeeType) + } + case strings.HasPrefix(option, "attestation_url="), strings.HasPrefix(option, "attestation-url="): + options.Convert = true + options.AttestationURL = strings.TrimPrefix(option, "attestation_url=") + if options.AttestationURL == option { + options.AttestationURL = strings.TrimPrefix(option, "attestation-url=") + } + case strings.HasPrefix(option, "passphrase="), strings.HasPrefix(option, "passphrase="): + options.Convert = true + options.DiskEncryptionPassphrase = strings.TrimPrefix(option, "passphrase=") + case strings.HasPrefix(option, "workload_id="), strings.HasPrefix(option, "workload-id="): + options.WorkloadID = strings.TrimPrefix(option, "workload_id=") + if options.WorkloadID == option { + options.WorkloadID = strings.TrimPrefix(option, "workload-id=") + } + case strings.HasPrefix(option, "cpus="): + options.CPUs, err = strconv.Atoi(strings.TrimPrefix(option, "cpus=")) + if err != nil { + return options, fmt.Errorf("parsing cpus= value %q: %w", strings.TrimPrefix(option, "cpus="), err) + } + case strings.HasPrefix(option, "memory="): + options.Memory, err = strconv.Atoi(strings.TrimPrefix(option, "memory=")) + if err != nil { + return options, fmt.Errorf("parsing memory= value %q: %w", strings.TrimPrefix(option, "memorys"), err) + } + case option == "ignore_attestation_errors", option == "ignore-attestation-errors": + options.IgnoreAttestationErrors = true + case strings.HasPrefix(option, "ignore_attestation_errors="), strings.HasPrefix(option, "ignore-attestation-errors="): + val := strings.TrimPrefix(option, "ignore_attestation_errors=") + if val == option { + val = strings.TrimPrefix(option, "ignore-attestation-errors=") + } + options.IgnoreAttestationErrors = val == "true" || val == "yes" || val == "on" || val == "1" + case strings.HasPrefix(option, "firmware-library="), strings.HasPrefix(option, "firmware_library="): + val := strings.TrimPrefix(option, "firmware-library=") + if val == option { + val = strings.TrimPrefix(option, "firmware_library=") + } + options.FirmwareLibrary = val + case strings.HasPrefix(option, "slop="): + options.Slop = strings.TrimPrefix(option, "slop=") + default: + knownOptions := []string{"type", "attestation_url", "passphrase", "workload_id", "cpus", "memory", "firmware_library", "slop"} + return options, fmt.Errorf("expected one or more of %q as arguments for --cw, not %q", knownOptions, option) + } + } + if options != defaults && !options.Convert { + return options, fmt.Errorf("--cw arguments missing one or more of (%q, %q)", "passphrase", "attestation_url") + } + return options, nil +} + // IDMappingOptions parses the build options related to user namespaces and ID mapping. func IDMappingOptions(c *cobra.Command, isolation define.Isolation) (usernsOptions define.NamespaceOptions, idmapOptions *define.IDMappingOptions, err error) { return IDMappingOptionsFromFlagSet(c.Flags(), c.PersistentFlags(), c.Flag) diff --git a/tests/mkcw.bats b/tests/mkcw.bats new file mode 100644 index 00000000000..69b23396ff9 --- /dev/null +++ b/tests/mkcw.bats @@ -0,0 +1,82 @@ +#!/usr/bin/env bats + +load helpers + +function mkcw_check_image() { + local imageID="$1" + local expectedEnv="$2" + # Mount the container and take a look at what it got from the image. + run_buildah from "$imageID" + ctrID="$output" + run_buildah mount "$ctrID" + mountpoint="$output" + # Should have a /disk.img file. + test -s "$mountpoint"/disk.img + # Should have a krun-sev.json file. + test -s "$mountpoint"/krun-sev.json + # Should have an executable entrypoint binary. + test -s "$mountpoint"/entrypoint + test -x "$mountpoint"/entrypoint + # Should have a sticky /tmp directory. + test -d "$mountpoint"/tmp + test -k "$mountpoint"/tmp + + # Decrypt, mount, and take a look around. + uuid=$(cryptsetup luksUUID "$mountpoint"/disk.img) + cryptsetup luksOpen --key-file "$TEST_SCRATCH_DIR"/key "$mountpoint"/disk.img "$uuid" + mkdir -p "$TEST_SCRATCH_DIR"/mount + mount /dev/mapper/"$uuid" "$TEST_SCRATCH_DIR"/mount + # Should have a not-empty config file with parts of an image's config. + test -s "$TEST_SCRATCH_DIR"/mount/.krun_config.json + if test -n "$expectedEnv" ; then + grep -q "expectedEnv" "$TEST_SCRATCH_DIR"/mount/.krun_config.json + fi + # Should have a /tmp directory, at least. + test -d "$TEST_SCRATCH_DIR"/mount/tmp + + # Clean up. + umount "$TEST_SCRATCH_DIR"/mount + cryptsetup luksClose "$uuid" + buildah umount "$ctrID" +} + +@test "mkcw-convert" { + skip_if_in_container + skip_if_rootless_environment + if ! which cryptsetup > /dev/null 2> /dev/null ; then + skip "cryptsetup not found" + fi + _prefetch busybox + + echo -n mkcw-convert > "$TEST_SCRATCH_DIR"/key + run_buildah mkcw --ignore-attestation-errors --passphrase=mkcw-convert busybox busybox-cw + mkcw_check_image busybox-cw +} + +@test "mkcw-commit" { + skip_if_in_container + skip_if_rootless_environment + if ! which cryptsetup > /dev/null 2> /dev/null ; then + skip "cryptsetup not found" + fi + _prefetch busybox + + echo -n "mkcw commit" > "$TEST_SCRATCH_DIR"/key + run_buildah from busybox + ctrID="$output" + run_buildah commit --iidfile "$TEST_SCRATCH_DIR"/iid --cw type=SEV,ignore_attestation_errors,passphrase="mkcw commit" "$ctrID" + mkcw_check_image $(cat "$TEST_SCRATCH_DIR"/iid) +} + +@test "mkcw build" { + skip_if_in_container + skip_if_rootless_environment + if ! which cryptsetup > /dev/null 2> /dev/null ; then + skip "cryptsetup not found" + fi + _prefetch alpine + + echo -n "mkcw build" > "$TEST_SCRATCH_DIR"/key + run_buildah build --iidfile "$TEST_SCRATCH_DIR"/iid --cw type=SEV,ignore_attestation_errors,passphrase="mkcw build" -f bud/env/Dockerfile.check-env bud/env + mkcw_check_image $(cat "$TEST_SCRATCH_DIR"/iid) +} diff --git a/vendor/github.com/aead/serpent/.gitignore b/vendor/github.com/aead/serpent/.gitignore new file mode 100644 index 00000000000..9d3d84373c4 --- /dev/null +++ b/vendor/github.com/aead/serpent/.gitignore @@ -0,0 +1,25 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test +.vscode + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/aead/serpent/LICENSE b/vendor/github.com/aead/serpent/LICENSE new file mode 100644 index 00000000000..b6a9210b9f6 --- /dev/null +++ b/vendor/github.com/aead/serpent/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Andreas Auernhammer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/aead/serpent/README.md b/vendor/github.com/aead/serpent/README.md new file mode 100644 index 00000000000..6dbceee8448 --- /dev/null +++ b/vendor/github.com/aead/serpent/README.md @@ -0,0 +1,9 @@ +[![Godoc Reference](https://godoc.org/github.com/aead/serpent?status.svg)](https://godoc.org/github.com/aead/serpent) + +## The Serpent block cipher + +Serpent is a symmetric key block cipher that was a finalist in the Advanced Encryption Standard (AES) contest, +where it was ranked second to Rijndael. Serpent was designed by Ross Anderson, Eli Biham, and Lars Knudsen. + +### Installation +Install in your GOPATH: `go get -u github.com/aead/serpent` diff --git a/vendor/github.com/aead/serpent/sbox_ref.go b/vendor/github.com/aead/serpent/sbox_ref.go new file mode 100644 index 00000000000..515afc69671 --- /dev/null +++ b/vendor/github.com/aead/serpent/sbox_ref.go @@ -0,0 +1,316 @@ +// Copyright (c) 2016 Andreas Auernhammer. All rights reserved. +// Use of this source code is governed by a license that can be +// found in the LICENSE file. + +package serpent + +// The linear transformation of serpent +// This version, tries not to minimize the +// number of registers, but maximize parallism. +func linear(v0, v1, v2, v3 *uint32) { + t0 := ((*v0 << 13) | (*v0 >> (32 - 13))) + t2 := ((*v2 << 3) | (*v2 >> (32 - 3))) + t1 := *v1 ^ t0 ^ t2 + t3 := *v3 ^ t2 ^ (t0 << 3) + *v1 = (t1 << 1) | (t1 >> (32 - 1)) + *v3 = (t3 << 7) | (t3 >> (32 - 7)) + t0 ^= *v1 ^ *v3 + t2 ^= *v3 ^ (*v1 << 7) + *v0 = (t0 << 5) | (t0 >> (32 - 5)) + *v2 = (t2 << 22) | (t2 >> (32 - 22)) +} + +// The inverse linear transformation of serpent +// This version, tries not to minimize the +// number of registers, but maximize parallism. +func linearInv(v0, v1, v2, v3 *uint32) { + t2 := (*v2 >> 22) | (*v2 << (32 - 22)) + t0 := (*v0 >> 5) | (*v0 << (32 - 5)) + t2 ^= *v3 ^ (*v1 << 7) + t0 ^= *v1 ^ *v3 + t3 := (*v3 >> 7) | (*v3 << (32 - 7)) + t1 := (*v1 >> 1) | (*v1 << (32 - 1)) + *v3 = t3 ^ t2 ^ (t0 << 3) + *v1 = t1 ^ t0 ^ t2 + *v2 = (t2 >> 3) | (t2 << (32 - 3)) + *v0 = (t0 >> 13) | (t0 << (32 - 13)) +} + +// The following functions sb0,sb1, ..., sb7 represent the 8 Serpent S-Boxes. +// sb0Inv til sb7Inv are the inverse functions (e.g. sb0Inv is the Inverse to sb0 +// and vice versa). +// The S-Boxes differ from the original Serpent definitions. This is for +// optimisation. The functions use the Serpent S-Box improvements for (non x86) +// from Dr. B. R. Gladman and Sam Simpson. + +// S-Box 0 +func sb0(r0, r1, r2, r3 *uint32) { + t0 := *r0 ^ *r3 + t1 := *r2 ^ t0 + t2 := *r1 ^ t1 + *r3 = (*r0 & *r3) ^ t2 + t3 := *r0 ^ (*r1 & t0) + *r2 = t2 ^ (*r2 | t3) + t4 := *r3 & (t1 ^ t3) + *r1 = (^t1) ^ t4 + *r0 = t4 ^ (^t3) +} + +// Inverse S-Box 0 +func sb0Inv(r0, r1, r2, r3 *uint32) { + t0 := ^(*r0) + t1 := *r0 ^ *r1 + t2 := *r3 ^ (t0 | t1) + t3 := *r2 ^ t2 + *r2 = t1 ^ t3 + t4 := t0 ^ (*r3 & t1) + *r1 = t2 ^ (*r2 & t4) + *r3 = (*r0 & t2) ^ (t3 | *r1) + *r0 = *r3 ^ (t3 ^ t4) +} + +// S-Box 1 +func sb1(r0, r1, r2, r3 *uint32) { + t0 := *r1 ^ (^(*r0)) + t1 := *r2 ^ (*r0 | t0) + *r2 = *r3 ^ t1 + t2 := *r1 ^ (*r3 | t0) + t3 := t0 ^ *r2 + *r3 = t3 ^ (t1 & t2) + t4 := t1 ^ t2 + *r1 = *r3 ^ t4 + *r0 = t1 ^ (t3 & t4) +} + +// Inverse S-Box 1 +func sb1Inv(r0, r1, r2, r3 *uint32) { + t0 := *r1 ^ *r3 + t1 := *r0 ^ (*r1 & t0) + t2 := t0 ^ t1 + *r3 = *r2 ^ t2 + t3 := *r1 ^ (t0 & t1) + t4 := *r3 | t3 + *r1 = t1 ^ t4 + t5 := ^(*r1) + t6 := *r3 ^ t3 + *r0 = t5 ^ t6 + *r2 = t2 ^ (t5 | t6) +} + +// S-Box 2 +func sb2(r0, r1, r2, r3 *uint32) { + v0 := *r0 // save r0 + v3 := *r3 // save r3 + t0 := ^v0 + t1 := *r1 ^ v3 + t2 := *r2 & t0 + *r0 = t1 ^ t2 + t3 := *r2 ^ t0 + t4 := *r2 ^ *r0 + t5 := *r1 & t4 + *r3 = t3 ^ t5 + *r2 = v0 ^ ((v3 | t5) & (*r0 | t3)) + *r1 = (t1 ^ *r3) ^ (*r2 ^ (v3 | t0)) +} + +// Inverse S-Box 2 +func sb2Inv(r0, r1, r2, r3 *uint32) { + v0 := *r0 // save r0 + v3 := *r3 // save r3 + t0 := *r1 ^ v3 + t1 := ^t0 + t2 := v0 ^ *r2 + t3 := *r2 ^ t0 + t4 := *r1 & t3 + *r0 = t2 ^ t4 + t5 := v0 | t1 + t6 := v3 ^ t5 + t7 := t2 | t6 + *r3 = t0 ^ t7 + t8 := ^t3 + t9 := *r0 | *r3 + *r1 = t8 ^ t9 + *r2 = (v3 & t8) ^ (t2 ^ t9) +} + +// S-Box 3 +func sb3(r0, r1, r2, r3 *uint32) { + v1 := *r1 // save r1 + v3 := *r3 // save r3 + t0 := *r0 ^ *r1 + t1 := *r0 & *r2 + t2 := *r0 | *r3 + t3 := *r2 ^ *r3 + t4 := t0 & t2 + t5 := t1 | t4 + *r2 = t3 ^ t5 + t6 := *r1 ^ t2 + t7 := t5 ^ t6 + t8 := t3 & t7 + *r0 = t0 ^ t8 + t9 := *r2 & *r0 + *r1 = t7 ^ t9 + *r3 = (v1 | v3) ^ (t3 ^ t9) +} + +// Inverse S-Box 3 +func sb3Inv(r0, r1, r2, r3 *uint32) { + t0 := *r0 | *r1 + t1 := *r1 ^ *r2 + t2 := *r1 & t1 + t3 := *r0 ^ t2 + t4 := *r2 ^ t3 + t5 := *r3 | t3 + *r0 = t1 ^ t5 + t6 := t1 | t5 + t7 := *r3 ^ t6 + *r2 = t4 ^ t7 + t8 := t0 ^ t7 + t9 := *r0 & t8 + *r3 = t3 ^ t9 + *r1 = *r3 ^ (*r0 ^ t8) +} + +// S-Box 4 +func sb4(r0, r1, r2, r3 *uint32) { + v0 := *r0 // save r0 + t0 := v0 ^ *r3 + t1 := *r3 & t0 + t2 := *r2 ^ t1 + t3 := *r1 | t2 + *r3 = t0 ^ t3 + t4 := ^(*r1) + t5 := t0 | t4 + *r0 = t2 ^ t5 + t6 := v0 & *r0 + t7 := t0 ^ t4 + t8 := t3 & t7 + *r2 = t6 ^ t8 + *r1 = (v0 ^ t2) ^ (t7 & *r2) +} + +// Inverse S-Box 4 +func sb4Inv(r0, r1, r2, r3 *uint32) { + v3 := *r3 // save r3 + t0 := *r2 | v3 + t1 := *r0 & t0 + t2 := *r1 ^ t1 + t3 := *r0 & t2 + t4 := *r2 ^ t3 + *r1 = v3 ^ t4 + t5 := ^(*r0) + t6 := t4 & *r1 + *r3 = t2 ^ t6 + t7 := *r1 | t5 + t8 := v3 ^ t7 + *r0 = *r3 ^ t8 + *r2 = (t2 & t8) ^ (*r1 ^ t5) +} + +// S-Box 5 +func sb5(r0, r1, r2, r3 *uint32) { + v1 := *r1 // save r1 + t0 := ^(*r0) + t1 := *r0 ^ v1 + t2 := *r0 ^ *r3 + t3 := *r2 ^ t0 + t4 := t1 | t2 + *r0 = t3 ^ t4 + t5 := *r3 & *r0 + t6 := t1 ^ *r0 + *r1 = t5 ^ t6 + t7 := t0 | *r0 + t8 := t1 | t5 + t9 := t2 ^ t7 + *r2 = t8 ^ t9 + *r3 = (v1 ^ t5) ^ (*r1 & t9) +} + +// Inverse S-Box 5 +func sb5Inv(r0, r1, r2, r3 *uint32) { + v0 := *r0 // save r0 + v1 := *r1 // save r1 + v3 := *r3 // save r3 + t0 := ^(*r2) + t1 := v1 & t0 + t2 := v3 ^ t1 + t3 := v0 & t2 + t4 := v1 ^ t0 + *r3 = t3 ^ t4 + t5 := v1 | *r3 + t6 := v0 & t5 + *r1 = t2 ^ t6 + t7 := v0 | v3 + t8 := t0 ^ t5 + *r0 = t7 ^ t8 + *r2 = (v1 & t7) ^ (t3 | (v0 ^ *r2)) +} + +// S-Box 6 +func sb6(r0, r1, r2, r3 *uint32) { + t0 := ^(*r0) + t1 := *r0 ^ *r3 + t2 := *r1 ^ t1 + t3 := t0 | t1 + t4 := *r2 ^ t3 + *r1 = *r1 ^ t4 + t5 := t1 | *r1 + t6 := *r3 ^ t5 + t7 := t4 & t6 + *r2 = t2 ^ t7 + t8 := t4 ^ t6 + *r0 = *r2 ^ t8 + *r3 = (^t4) ^ (t2 & t8) +} + +// Inverse S-Box 6 +func sb6Inv(r0, r1, r2, r3 *uint32) { + v1 := *r1 // save r1 + v3 := *r3 // save r3 + t0 := ^(*r0) + t1 := *r0 ^ v1 + t2 := *r2 ^ t1 + t3 := *r2 | t0 + t4 := v3 ^ t3 + *r1 = t2 ^ t4 + t5 := t2 & t4 + t6 := t1 ^ t5 + t7 := v1 | t6 + *r3 = t4 ^ t7 + t8 := v1 | *r3 + *r0 = t6 ^ t8 + *r2 = (v3 & t0) ^ (t2 ^ t8) +} + +// S-Box 7 +func sb7(r0, r1, r2, r3 *uint32) { + t0 := *r1 ^ *r2 + t1 := *r2 & t0 + t2 := *r3 ^ t1 + t3 := *r0 ^ t2 + t4 := *r3 | t0 + t5 := t3 & t4 + *r1 = *r1 ^ t5 + t6 := t2 | *r1 + t7 := *r0 & t3 + *r3 = t0 ^ t7 + t8 := t3 ^ t6 + t9 := *r3 & t8 + *r2 = t2 ^ t9 + *r0 = (^t8) ^ (*r3 & *r2) +} + +// Inverse S-Box 7 +func sb7Inv(r0, r1, r2, r3 *uint32) { + v0 := *r0 // save r0 + v3 := *r3 // save r3 + t0 := *r2 | (v0 & *r1) + t1 := v3 & (v0 | *r1) + *r3 = t0 ^ t1 + t2 := ^v3 + t3 := *r1 ^ t1 + t4 := t3 | (*r3 ^ t2) + *r1 = v0 ^ t4 + *r0 = (*r2 ^ t3) ^ (v3 | *r1) + *r2 = (t0 ^ *r1) ^ (*r0 ^ (v0 & *r3)) +} diff --git a/vendor/github.com/aead/serpent/serpent.go b/vendor/github.com/aead/serpent/serpent.go new file mode 100644 index 00000000000..b3fb811dec3 --- /dev/null +++ b/vendor/github.com/aead/serpent/serpent.go @@ -0,0 +1,119 @@ +// Copyright (c) 2016 Andreas Auernhammer. All rights reserved. +// Use of this source code is governed by a license that can be +// found in the LICENSE file. + +// Package serpent implements the Serpent block cipher +// submitted to the AES challenge. Serpent was designed by +// Ross Anderson, Eli Biham und Lars Knudsen. +// The block cipher takes a 128, 192 or 256 bit key and +// has a block size of 128 bit. +package serpent // import "github.com/aead/serpent" + +import ( + "crypto/cipher" + "errors" +) + +// BlockSize is the serpent block size in bytes. +const BlockSize = 16 + +const phi = 0x9e3779b9 // The Serpent phi constant (sqrt(5) - 1) * 2**31 + +var errKeySize = errors.New("invalid key size") + +// NewCipher returns a new cipher.Block implementing the serpent block cipher. +// The key argument must be 128, 192 or 256 bit (16, 24, 32 byte). +func NewCipher(key []byte) (cipher.Block, error) { + if k := len(key); k != 16 && k != 24 && k != 32 { + return nil, errKeySize + } + s := &subkeys{} + s.keySchedule(key) + return s, nil +} + +// The 132 32 bit subkeys of serpent +type subkeys [132]uint32 + +func (s *subkeys) BlockSize() int { return BlockSize } + +func (s *subkeys) Encrypt(dst, src []byte) { + if len(src) < BlockSize { + panic("src buffer to small") + } + if len(dst) < BlockSize { + panic("dst buffer to small") + } + encryptBlock(dst, src, s) +} + +func (s *subkeys) Decrypt(dst, src []byte) { + if len(src) < BlockSize { + panic("src buffer to small") + } + if len(dst) < BlockSize { + panic("dst buffer to small") + } + decryptBlock(dst, src, s) +} + +// The key schedule of serpent. +func (s *subkeys) keySchedule(key []byte) { + var k [16]uint32 + j := 0 + for i := 0; i+4 <= len(key); i += 4 { + k[j] = uint32(key[i]) | uint32(key[i+1])<<8 | uint32(key[i+2])<<16 | uint32(key[i+3])<<24 + j++ + } + if j < 8 { + k[j] = 1 + } + + for i := 8; i < 16; i++ { + x := k[i-8] ^ k[i-5] ^ k[i-3] ^ k[i-1] ^ phi ^ uint32(i-8) + k[i] = (x << 11) | (x >> 21) + s[i-8] = k[i] + } + for i := 8; i < 132; i++ { + x := s[i-8] ^ s[i-5] ^ s[i-3] ^ s[i-1] ^ phi ^ uint32(i) + s[i] = (x << 11) | (x >> 21) + } + + sb3(&s[0], &s[1], &s[2], &s[3]) + sb2(&s[4], &s[5], &s[6], &s[7]) + sb1(&s[8], &s[9], &s[10], &s[11]) + sb0(&s[12], &s[13], &s[14], &s[15]) + sb7(&s[16], &s[17], &s[18], &s[19]) + sb6(&s[20], &s[21], &s[22], &s[23]) + sb5(&s[24], &s[25], &s[26], &s[27]) + sb4(&s[28], &s[29], &s[30], &s[31]) + + sb3(&s[32], &s[33], &s[34], &s[35]) + sb2(&s[36], &s[37], &s[38], &s[39]) + sb1(&s[40], &s[41], &s[42], &s[43]) + sb0(&s[44], &s[45], &s[46], &s[47]) + sb7(&s[48], &s[49], &s[50], &s[51]) + sb6(&s[52], &s[53], &s[54], &s[55]) + sb5(&s[56], &s[57], &s[58], &s[59]) + sb4(&s[60], &s[61], &s[62], &s[63]) + + sb3(&s[64], &s[65], &s[66], &s[67]) + sb2(&s[68], &s[69], &s[70], &s[71]) + sb1(&s[72], &s[73], &s[74], &s[75]) + sb0(&s[76], &s[77], &s[78], &s[79]) + sb7(&s[80], &s[81], &s[82], &s[83]) + sb6(&s[84], &s[85], &s[86], &s[87]) + sb5(&s[88], &s[89], &s[90], &s[91]) + sb4(&s[92], &s[93], &s[94], &s[95]) + + sb3(&s[96], &s[97], &s[98], &s[99]) + sb2(&s[100], &s[101], &s[102], &s[103]) + sb1(&s[104], &s[105], &s[106], &s[107]) + sb0(&s[108], &s[109], &s[110], &s[111]) + sb7(&s[112], &s[113], &s[114], &s[115]) + sb6(&s[116], &s[117], &s[118], &s[119]) + sb5(&s[120], &s[121], &s[122], &s[123]) + sb4(&s[124], &s[125], &s[126], &s[127]) + + sb3(&s[128], &s[129], &s[130], &s[131]) +} diff --git a/vendor/github.com/aead/serpent/serpent_ref.go b/vendor/github.com/aead/serpent/serpent_ref.go new file mode 100644 index 00000000000..2d3ff02a666 --- /dev/null +++ b/vendor/github.com/aead/serpent/serpent_ref.go @@ -0,0 +1,276 @@ +// Copyright (c) 2016 Andreas Auernhammer. All rights reserved. +// Use of this source code is governed by a license that can be +// found in the LICENSE file. + +package serpent + +// Encrypts one block with the given 132 sub-keys sk. +func encryptBlock(dst, src []byte, sk *subkeys) { + // Transform the input block to 4 x 32 bit registers + r0 := uint32(src[0]) | uint32(src[1])<<8 | uint32(src[2])<<16 | uint32(src[3])<<24 + r1 := uint32(src[4]) | uint32(src[5])<<8 | uint32(src[6])<<16 | uint32(src[7])<<24 + r2 := uint32(src[8]) | uint32(src[9])<<8 | uint32(src[10])<<16 | uint32(src[11])<<24 + r3 := uint32(src[12]) | uint32(src[13])<<8 | uint32(src[14])<<16 | uint32(src[15])<<24 + + // Encrypt the block with the 132 sub-keys and 8 S-Boxes + r0, r1, r2, r3 = r0^sk[0], r1^sk[1], r2^sk[2], r3^sk[3] + sb0(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[4], r1^sk[5], r2^sk[6], r3^sk[7] + sb1(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[8], r1^sk[9], r2^sk[10], r3^sk[11] + sb2(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[12], r1^sk[13], r2^sk[14], r3^sk[15] + sb3(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[16], r1^sk[17], r2^sk[18], r3^sk[19] + sb4(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[20], r1^sk[21], r2^sk[22], r3^sk[23] + sb5(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[24], r1^sk[25], r2^sk[26], r3^sk[27] + sb6(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[28], r1^sk[29], r2^sk[30], r3^sk[31] + sb7(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + + r0, r1, r2, r3 = r0^sk[32], r1^sk[33], r2^sk[34], r3^sk[35] + sb0(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[36], r1^sk[37], r2^sk[38], r3^sk[39] + sb1(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[40], r1^sk[41], r2^sk[42], r3^sk[43] + sb2(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[44], r1^sk[45], r2^sk[46], r3^sk[47] + sb3(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[48], r1^sk[49], r2^sk[50], r3^sk[51] + sb4(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[52], r1^sk[53], r2^sk[54], r3^sk[55] + sb5(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[56], r1^sk[57], r2^sk[58], r3^sk[59] + sb6(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[60], r1^sk[61], r2^sk[62], r3^sk[63] + sb7(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + + r0, r1, r2, r3 = r0^sk[64], r1^sk[65], r2^sk[66], r3^sk[67] + sb0(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[68], r1^sk[69], r2^sk[70], r3^sk[71] + sb1(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[72], r1^sk[73], r2^sk[74], r3^sk[75] + sb2(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[76], r1^sk[77], r2^sk[78], r3^sk[79] + sb3(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[80], r1^sk[81], r2^sk[82], r3^sk[83] + sb4(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[84], r1^sk[85], r2^sk[86], r3^sk[87] + sb5(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[88], r1^sk[89], r2^sk[90], r3^sk[91] + sb6(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[92], r1^sk[93], r2^sk[94], r3^sk[95] + sb7(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + + r0, r1, r2, r3 = r0^sk[96], r1^sk[97], r2^sk[98], r3^sk[99] + sb0(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[100], r1^sk[101], r2^sk[102], r3^sk[103] + sb1(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[104], r1^sk[105], r2^sk[106], r3^sk[107] + sb2(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[108], r1^sk[109], r2^sk[110], r3^sk[111] + sb3(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[112], r1^sk[113], r2^sk[114], r3^sk[115] + sb4(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[116], r1^sk[117], r2^sk[118], r3^sk[119] + sb5(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[120], r1^sk[121], r2^sk[122], r3^sk[123] + sb6(&r0, &r1, &r2, &r3) + linear(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[124], r1^sk[125], r2^sk[126], r3^sk[127] + sb7(&r0, &r1, &r2, &r3) + + // whitening + r0 ^= sk[128] + r1 ^= sk[129] + r2 ^= sk[130] + r3 ^= sk[131] + + // write the encrypted block to the output + + dst[0] = byte(r0) + dst[1] = byte(r0 >> 8) + dst[2] = byte(r0 >> 16) + dst[3] = byte(r0 >> 24) + dst[4] = byte(r1) + dst[5] = byte(r1 >> 8) + dst[6] = byte(r1 >> 16) + dst[7] = byte(r1 >> 24) + dst[8] = byte(r2) + dst[9] = byte(r2 >> 8) + dst[10] = byte(r2 >> 16) + dst[11] = byte(r2 >> 24) + dst[12] = byte(r3) + dst[13] = byte(r3 >> 8) + dst[14] = byte(r3 >> 16) + dst[15] = byte(r3 >> 24) +} + +// Decrypts one block with the given 132 sub-keys sk. +func decryptBlock(dst, src []byte, sk *subkeys) { + // Transform the input block to 4 x 32 bit registers + r0 := uint32(src[0]) | uint32(src[1])<<8 | uint32(src[2])<<16 | uint32(src[3])<<24 + r1 := uint32(src[4]) | uint32(src[5])<<8 | uint32(src[6])<<16 | uint32(src[7])<<24 + r2 := uint32(src[8]) | uint32(src[9])<<8 | uint32(src[10])<<16 | uint32(src[11])<<24 + r3 := uint32(src[12]) | uint32(src[13])<<8 | uint32(src[14])<<16 | uint32(src[15])<<24 + + // undo whitening + r0 ^= sk[128] + r1 ^= sk[129] + r2 ^= sk[130] + r3 ^= sk[131] + + // Decrypt the block with the 132 sub-keys and 8 S-Boxes + sb7Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[124], r1^sk[125], r2^sk[126], r3^sk[127] + linearInv(&r0, &r1, &r2, &r3) + sb6Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[120], r1^sk[121], r2^sk[122], r3^sk[123] + linearInv(&r0, &r1, &r2, &r3) + sb5Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[116], r1^sk[117], r2^sk[118], r3^sk[119] + linearInv(&r0, &r1, &r2, &r3) + sb4Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[112], r1^sk[113], r2^sk[114], r3^sk[115] + linearInv(&r0, &r1, &r2, &r3) + sb3Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[108], r1^sk[109], r2^sk[110], r3^sk[111] + linearInv(&r0, &r1, &r2, &r3) + sb2Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[104], r1^sk[105], r2^sk[106], r3^sk[107] + linearInv(&r0, &r1, &r2, &r3) + sb1Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[100], r1^sk[101], r2^sk[102], r3^sk[103] + linearInv(&r0, &r1, &r2, &r3) + sb0Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[96], r1^sk[97], r2^sk[98], r3^sk[99] + linearInv(&r0, &r1, &r2, &r3) + + sb7Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[92], r1^sk[93], r2^sk[94], r3^sk[95] + linearInv(&r0, &r1, &r2, &r3) + sb6Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[88], r1^sk[89], r2^sk[90], r3^sk[91] + linearInv(&r0, &r1, &r2, &r3) + sb5Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[84], r1^sk[85], r2^sk[86], r3^sk[87] + linearInv(&r0, &r1, &r2, &r3) + sb4Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[80], r1^sk[81], r2^sk[82], r3^sk[83] + linearInv(&r0, &r1, &r2, &r3) + sb3Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[76], r1^sk[77], r2^sk[78], r3^sk[79] + linearInv(&r0, &r1, &r2, &r3) + sb2Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[72], r1^sk[73], r2^sk[74], r3^sk[75] + linearInv(&r0, &r1, &r2, &r3) + sb1Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[68], r1^sk[69], r2^sk[70], r3^sk[71] + linearInv(&r0, &r1, &r2, &r3) + sb0Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[64], r1^sk[65], r2^sk[66], r3^sk[67] + linearInv(&r0, &r1, &r2, &r3) + + sb7Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[60], r1^sk[61], r2^sk[62], r3^sk[63] + linearInv(&r0, &r1, &r2, &r3) + sb6Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[56], r1^sk[57], r2^sk[58], r3^sk[59] + linearInv(&r0, &r1, &r2, &r3) + sb5Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[52], r1^sk[53], r2^sk[54], r3^sk[55] + linearInv(&r0, &r1, &r2, &r3) + sb4Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[48], r1^sk[49], r2^sk[50], r3^sk[51] + linearInv(&r0, &r1, &r2, &r3) + sb3Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[44], r1^sk[45], r2^sk[46], r3^sk[47] + linearInv(&r0, &r1, &r2, &r3) + sb2Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[40], r1^sk[41], r2^sk[42], r3^sk[43] + linearInv(&r0, &r1, &r2, &r3) + sb1Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[36], r1^sk[37], r2^sk[38], r3^sk[39] + linearInv(&r0, &r1, &r2, &r3) + sb0Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[32], r1^sk[33], r2^sk[34], r3^sk[35] + linearInv(&r0, &r1, &r2, &r3) + + sb7Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[28], r1^sk[29], r2^sk[30], r3^sk[31] + linearInv(&r0, &r1, &r2, &r3) + sb6Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[24], r1^sk[25], r2^sk[26], r3^sk[27] + linearInv(&r0, &r1, &r2, &r3) + sb5Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[20], r1^sk[21], r2^sk[22], r3^sk[23] + linearInv(&r0, &r1, &r2, &r3) + sb4Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[16], r1^sk[17], r2^sk[18], r3^sk[19] + linearInv(&r0, &r1, &r2, &r3) + sb3Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[12], r1^sk[13], r2^sk[14], r3^sk[15] + linearInv(&r0, &r1, &r2, &r3) + sb2Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[8], r1^sk[9], r2^sk[10], r3^sk[11] + linearInv(&r0, &r1, &r2, &r3) + sb1Inv(&r0, &r1, &r2, &r3) + r0, r1, r2, r3 = r0^sk[4], r1^sk[5], r2^sk[6], r3^sk[7] + linearInv(&r0, &r1, &r2, &r3) + sb0Inv(&r0, &r1, &r2, &r3) + + r0 ^= sk[0] + r1 ^= sk[1] + r2 ^= sk[2] + r3 ^= sk[3] + + // write the decrypted block to the output + dst[0] = byte(r0) + dst[1] = byte(r0 >> 8) + dst[2] = byte(r0 >> 16) + dst[3] = byte(r0 >> 24) + dst[4] = byte(r1) + dst[5] = byte(r1 >> 8) + dst[6] = byte(r1 >> 16) + dst[7] = byte(r1 >> 24) + dst[8] = byte(r2) + dst[9] = byte(r2 >> 8) + dst[10] = byte(r2 >> 16) + dst[11] = byte(r2 >> 24) + dst[12] = byte(r3) + dst[13] = byte(r3 >> 8) + dst[14] = byte(r3 >> 16) + dst[15] = byte(r3 >> 24) +} diff --git a/vendor/github.com/containers/luksy/.cirrus.yml b/vendor/github.com/containers/luksy/.cirrus.yml new file mode 100644 index 00000000000..a7e74a5608a --- /dev/null +++ b/vendor/github.com/containers/luksy/.cirrus.yml @@ -0,0 +1,16 @@ +docker_builder: + name: CI + env: + HOME: /root + DEBIAN_FRONTEND: noninteractive + setup_script: | + apt-get -q update + apt-get -q install -y bats cryptsetup golang + go version + make + unit_test_script: + go test -v -cover + defaults_script: | + bats -f defaults ./tests + aes_script: | + bats -f aes ./tests diff --git a/vendor/github.com/containers/luksy/.dockerignore b/vendor/github.com/containers/luksy/.dockerignore new file mode 100644 index 00000000000..242763031f6 --- /dev/null +++ b/vendor/github.com/containers/luksy/.dockerignore @@ -0,0 +1,2 @@ +lukstool +lukstool.test diff --git a/vendor/github.com/containers/luksy/.gitignore b/vendor/github.com/containers/luksy/.gitignore new file mode 100644 index 00000000000..3b735ec4a8c --- /dev/null +++ b/vendor/github.com/containers/luksy/.gitignore @@ -0,0 +1,21 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work diff --git a/vendor/github.com/containers/luksy/LICENSE b/vendor/github.com/containers/luksy/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/vendor/github.com/containers/luksy/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/containers/luksy/Makefile b/vendor/github.com/containers/luksy/Makefile new file mode 100644 index 00000000000..f958505fb8e --- /dev/null +++ b/vendor/github.com/containers/luksy/Makefile @@ -0,0 +1,14 @@ +GO = go +BATS = bats + +all: luksy + +luksy: cmd/luksy/*.go *.go + $(GO) build -o luksy ./cmd/luksy + +clean: + $(RM) luksy luksy.test + +test: + $(GO) test + $(BATS) ./tests diff --git a/vendor/github.com/containers/luksy/README.md b/vendor/github.com/containers/luksy/README.md new file mode 100644 index 00000000000..2bf3a4387f2 --- /dev/null +++ b/vendor/github.com/containers/luksy/README.md @@ -0,0 +1,10 @@ +luksy: offline encryption/decryption using LUKS formats [![Cirrus CI Status](https://img.shields.io/cirrus/github/containers/luksy/main)](https://cirrus-ci.com/github/containers/luksy/main) +- +luksy implements encryption and decryption using LUKSv1 and LUKSv2 formats. +Think of it as a clunkier cousin of gzip/bzip2/xz that doesn't actually produce +smaller output than input, but it encrypts, and that's nice. + +* The main goal is to be able to encrypt/decrypt when we don't have access to + the Linux device mapper. Duplicating functions of cryptsetup that it can + perform without accessing the Linux device mapper is not a priority. +* If you can use cryptsetup instead, use cryptsetup instead. diff --git a/vendor/github.com/containers/luksy/decrypt.go b/vendor/github.com/containers/luksy/decrypt.go new file mode 100644 index 00000000000..45b66b6e0e2 --- /dev/null +++ b/vendor/github.com/containers/luksy/decrypt.go @@ -0,0 +1,244 @@ +package luksy + +import ( + "bytes" + "errors" + "fmt" + "os" + "strconv" + + "golang.org/x/crypto/argon2" + "golang.org/x/crypto/pbkdf2" +) + +// Decrypt attempts to verify the specified password using information from the +// header and read from the specified file. +// +// Returns a function which will decrypt payload blocks in succession, the size +// of chunks of data that the function expects, the offset in the file where +// the payload begins, and the size of the payload. +func (h V1Header) Decrypt(password string, f *os.File) (func([]byte) ([]byte, error), int, int64, int64, error) { + st, err := f.Stat() + if err != nil { + return nil, -1, -1, -1, err + } + hasher, err := hasherByName(h.HashSpec()) + if err != nil { + return nil, -1, -1, -1, fmt.Errorf("unsupported digest algorithm %q: %w", h.HashSpec(), err) + } + + activeKeys := 0 + for k := 0; k < v1NumKeys; k++ { + keyslot, err := h.KeySlot(k) + if err != nil { + return nil, -1, -1, -1, fmt.Errorf("reading key slot %d: %w", k, err) + } + active, err := keyslot.Active() + if err != nil { + return nil, -1, -1, -1, fmt.Errorf("checking if key slot %d is active: %w", k, err) + } + if !active { + continue + } + activeKeys++ + + passwordDerived := pbkdf2.Key([]byte(password), keyslot.KeySlotSalt(), int(keyslot.Iterations()), int(h.KeyBytes()), hasher) + striped := make([]byte, h.KeyBytes()*keyslot.Stripes()) + n, err := f.ReadAt(striped, int64(keyslot.KeyMaterialOffset())*V1SectorSize) + if err != nil { + return nil, -1, -1, -1, fmt.Errorf("reading diffuse material for keyslot %d: %w", k, err) + } + if n != len(striped) { + return nil, -1, -1, -1, fmt.Errorf("short read while reading diffuse material for keyslot %d: expected %d, got %d", k, len(striped), n) + } + splitKey, err := v1decrypt(h.CipherName(), h.CipherMode(), 0, passwordDerived, striped, V1SectorSize, false) + if err != nil { + fmt.Fprintf(os.Stderr, "error attempting to decrypt main key: %v\n", err) + continue + } + mkCandidate, err := afMerge(splitKey, hasher(), int(h.KeyBytes()), int(keyslot.Stripes())) + if err != nil { + fmt.Fprintf(os.Stderr, "error attempting to compute main key: %v\n", err) + continue + } + mkcandidateDerived := pbkdf2.Key(mkCandidate, h.MKDigestSalt(), int(h.MKDigestIter()), v1DigestSize, hasher) + ivTweak := 0 + decryptStream := func(ciphertext []byte) ([]byte, error) { + plaintext, err := v1decrypt(h.CipherName(), h.CipherMode(), ivTweak, mkCandidate, ciphertext, V1SectorSize, false) + ivTweak += len(ciphertext) / V1SectorSize + return plaintext, err + } + if bytes.Equal(mkcandidateDerived, h.MKDigest()) { + payloadOffset := int64(h.PayloadOffset() * V1SectorSize) + return decryptStream, V1SectorSize, payloadOffset, st.Size() - payloadOffset, nil + } + } + if activeKeys == 0 { + return nil, -1, -1, -1, errors.New("no passwords set on LUKS1 volume") + } + return nil, -1, -1, -1, errors.New("decryption error: incorrect password") +} + +// Decrypt attempts to verify the specified password using information from the +// header, JSON block, and read from the specified file. +// +// Returns a function which will decrypt payload blocks in succession, the size +// of chunks of data that the function expects, the offset in the file where +// the payload begins, and the size of the payload. +func (h V2Header) Decrypt(password string, f *os.File, j V2JSON) (func([]byte) ([]byte, error), int, int64, int64, error) { + foundDigests := 0 + for d, digest := range j.Digests { + if digest.Type != "pbkdf2" { + continue + } + if digest.V2JSONDigestPbkdf2 == nil { + return nil, -1, -1, -1, fmt.Errorf("digest %q is corrupt: no pbkdf2 parameters", d) + } + foundDigests++ + if len(digest.Segments) == 0 || len(digest.Digest) == 0 { + continue + } + payloadOffset := int64(-1) + payloadSectorSize := V1SectorSize + payloadEncryption := "" + payloadSize := int64(0) + ivTweak := 0 + for _, segmentID := range digest.Segments { + segment, ok := j.Segments[segmentID] + if !ok { + continue // well, that was misleading + } + if segment.Type != "crypt" { + continue + } + tmp, err := strconv.ParseInt(segment.Offset, 10, 64) + if err != nil { + continue + } + payloadOffset = tmp + if segment.Size == "dynamic" { + st, err := f.Stat() + if err != nil { + continue + } + payloadSize = st.Size() - payloadOffset + } else { + payloadSize, err = strconv.ParseInt(segment.Size, 10, 64) + if err != nil { + continue + } + } + payloadSectorSize = segment.SectorSize + payloadEncryption = segment.Encryption + ivTweak = segment.IVTweak + break + } + if payloadEncryption == "" { + continue + } + activeKeys := 0 + for k, keyslot := range j.Keyslots { + if keyslot.Priority != nil && *keyslot.Priority == V2JSONKeyslotPriorityIgnore { + continue + } + applicable := true + if len(digest.Keyslots) > 0 { + applicable = false + for i := 0; i < len(digest.Keyslots); i++ { + if k == digest.Keyslots[i] { + applicable = true + break + } + } + } + if !applicable { + continue + } + if keyslot.Type != "luks2" { + continue + } + if keyslot.V2JSONKeyslotLUKS2 == nil { + return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt", k) + } + if keyslot.V2JSONKeyslotLUKS2.AF.Type != "luks1" { + continue + } + if keyslot.V2JSONKeyslotLUKS2.AF.V2JSONAFLUKS1 == nil { + return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: no AF parameters", k) + } + if keyslot.Area.Type != "raw" { + return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: key data area is not raw", k) + } + if keyslot.Area.KeySize*V2SectorSize < keyslot.KeySize*keyslot.AF.Stripes { + return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: key data area is too small (%d < %d)", k, keyslot.Area.KeySize*V2SectorSize, keyslot.KeySize*keyslot.AF.Stripes) + } + var passwordDerived []byte + switch keyslot.V2JSONKeyslotLUKS2.Kdf.Type { + default: + continue + case "pbkdf2": + if keyslot.V2JSONKeyslotLUKS2.Kdf.V2JSONKdfPbkdf2 == nil { + return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: no pbkdf2 parameters", k) + } + hasher, err := hasherByName(keyslot.Kdf.Hash) + if err != nil { + return nil, -1, -1, -1, fmt.Errorf("unsupported digest algorithm %q: %w", keyslot.Kdf.Hash, err) + } + passwordDerived = pbkdf2.Key([]byte(password), keyslot.Kdf.Salt, keyslot.Kdf.Iterations, keyslot.KeySize, hasher) + case "argon2i": + if keyslot.V2JSONKeyslotLUKS2.Kdf.V2JSONKdfArgon2i == nil { + return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: no argon2i parameters", k) + } + passwordDerived = argon2.Key([]byte(password), keyslot.Kdf.Salt, uint32(keyslot.Kdf.Time), uint32(keyslot.Kdf.Memory), uint8(keyslot.Kdf.CPUs), uint32(keyslot.KeySize)) + case "argon2id": + if keyslot.V2JSONKeyslotLUKS2.Kdf.V2JSONKdfArgon2i == nil { + return nil, -1, -1, -1, fmt.Errorf("key slot %q is corrupt: no argon2id parameters", k) + } + passwordDerived = argon2.IDKey([]byte(password), keyslot.Kdf.Salt, uint32(keyslot.Kdf.Time), uint32(keyslot.Kdf.Memory), uint8(keyslot.Kdf.CPUs), uint32(keyslot.KeySize)) + } + striped := make([]byte, keyslot.KeySize*keyslot.AF.Stripes) + n, err := f.ReadAt(striped, int64(keyslot.Area.Offset)) + if err != nil { + return nil, -1, -1, -1, fmt.Errorf("reading diffuse material for keyslot %q: %w", k, err) + } + if n != len(striped) { + return nil, -1, -1, -1, fmt.Errorf("short read while reading diffuse material for keyslot %q: expected %d, got %d", k, len(striped), n) + } + splitKey, err := v2decrypt(keyslot.Area.Encryption, 0, passwordDerived, striped, V1SectorSize, false) + if err != nil { + fmt.Fprintf(os.Stderr, "error attempting to decrypt main key: %v\n", err) + continue + } + afhasher, err := hasherByName(keyslot.AF.Hash) + if err != nil { + return nil, -1, -1, -1, fmt.Errorf("unsupported digest algorithm %q: %w", keyslot.AF.Hash, err) + } + mkCandidate, err := afMerge(splitKey, afhasher(), int(keyslot.KeySize), int(keyslot.AF.Stripes)) + if err != nil { + fmt.Fprintf(os.Stderr, "error attempting to compute main key: %v\n", err) + continue + } + digester, err := hasherByName(digest.Hash) + if err != nil { + return nil, -1, -1, -1, fmt.Errorf("unsupported digest algorithm %q: %w", digest.Hash, err) + } + mkcandidateDerived := pbkdf2.Key(mkCandidate, digest.Salt, digest.Iterations, len(digest.Digest), digester) + decryptStream := func(ciphertext []byte) ([]byte, error) { + plaintext, err := v2decrypt(payloadEncryption, ivTweak, mkCandidate, ciphertext, payloadSectorSize, true) + ivTweak += len(ciphertext) / payloadSectorSize + return plaintext, err + } + if bytes.Equal(mkcandidateDerived, digest.Digest) { + return decryptStream, payloadSectorSize, payloadOffset, payloadSize, nil + } + activeKeys++ + } + if activeKeys == 0 { + return nil, -1, -1, -1, fmt.Errorf("no passwords set on LUKS2 volume for digest %q", d) + } + } + if foundDigests == 0 { + return nil, -1, -1, -1, errors.New("no usable password-verification digests set on LUKS2 volume") + } + return nil, -1, -1, -1, errors.New("decryption error: incorrect password") +} diff --git a/vendor/github.com/containers/luksy/encrypt.go b/vendor/github.com/containers/luksy/encrypt.go new file mode 100644 index 00000000000..97e5a597fd8 --- /dev/null +++ b/vendor/github.com/containers/luksy/encrypt.go @@ -0,0 +1,421 @@ +package luksy + +import ( + "crypto/rand" + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + + "github.com/google/uuid" + "golang.org/x/crypto/argon2" + "golang.org/x/crypto/pbkdf2" +) + +// EncryptV1 prepares to encrypt data using one or more passwords and the +// specified cipher (or a default, if the specified cipher is ""). +// +// Returns a fixed LUKSv1 header which contains keying information, a function +// which will encrypt blocks of data in succession, and the size of chunks of +// data that it expects. +func EncryptV1(password []string, cipher string) ([]byte, func([]byte) ([]byte, error), int, error) { + if len(password) == 0 { + return nil, nil, -1, errors.New("at least one password is required") + } + if len(password) > v1NumKeys { + return nil, nil, -1, fmt.Errorf("attempted to use %d passwords, only %d possible", len(password), v1NumKeys) + } + if cipher == "" { + cipher = "aes-xts-plain64" + } + + salt := make([]byte, v1SaltSize) + n, err := rand.Read(salt) + if err != nil { + return nil, nil, -1, fmt.Errorf("reading random data: %w", err) + } + if n != len(salt) { + return nil, nil, -1, errors.New("short read") + } + + cipherSpec := strings.SplitN(cipher, "-", 3) + if len(cipherSpec) != 3 || len(cipherSpec[0]) == 0 || len(cipherSpec[1]) == 0 || len(cipherSpec[2]) == 0 { + return nil, nil, -1, fmt.Errorf("invalid cipher %q", cipher) + } + + var h V1Header + if err := h.SetMagic(V1Magic); err != nil { + return nil, nil, -1, fmt.Errorf("setting magic to v1: %w", err) + } + if err := h.SetVersion(1); err != nil { + return nil, nil, -1, fmt.Errorf("setting version to 1: %w", err) + } + h.SetCipherName(cipherSpec[0]) + h.SetCipherMode(cipherSpec[1] + "-" + cipherSpec[2]) + h.SetHashSpec("sha256") + h.SetKeyBytes(32) + if cipherSpec[1] == "xts" { + h.SetKeyBytes(64) + } + h.SetMKDigestSalt(salt) + h.SetMKDigestIter(V1Stripes) + h.SetUUID(uuid.NewString()) + + mkey := make([]byte, h.KeyBytes()) + n, err = rand.Read(mkey) + if err != nil { + return nil, nil, -1, fmt.Errorf("reading random data: %w", err) + } + if n != len(mkey) { + return nil, nil, -1, errors.New("short read") + } + + hasher, err := hasherByName(h.HashSpec()) + if err != nil { + return nil, nil, -1, errors.New("internal error") + } + + mkdigest := pbkdf2.Key(mkey, h.MKDigestSalt(), int(h.MKDigestIter()), v1DigestSize, hasher) + h.SetMKDigest(mkdigest) + + headerLength := roundUpToMultiple(v1HeaderStructSize, V1AlignKeyslots) + iterations := IterationsPBKDF2(salt, int(h.KeyBytes()), hasher) + var stripes [][]byte + ksSalt := make([]byte, v1KeySlotSaltLength) + for i := 0; i < v1NumKeys; i++ { + n, err = rand.Read(ksSalt) + if err != nil { + return nil, nil, -1, fmt.Errorf("reading random data: %w", err) + } + if n != len(ksSalt) { + return nil, nil, -1, errors.New("short read") + } + var keyslot V1KeySlot + keyslot.SetActive(i < len(password)) + keyslot.SetIterations(uint32(iterations)) + keyslot.SetStripes(V1Stripes) + keyslot.SetKeySlotSalt(ksSalt) + if i < len(password) { + splitKey, err := afSplit(mkey, hasher(), int(h.MKDigestIter())) + if err != nil { + return nil, nil, -1, fmt.Errorf("splitting key: %w", err) + } + passwordDerived := pbkdf2.Key([]byte(password[i]), keyslot.KeySlotSalt(), int(keyslot.Iterations()), int(h.KeyBytes()), hasher) + striped, err := v1encrypt(h.CipherName(), h.CipherMode(), 0, passwordDerived, splitKey, V1SectorSize, false) + if err != nil { + return nil, nil, -1, fmt.Errorf("encrypting split key with password: %w", err) + } + if len(striped) != len(mkey)*int(keyslot.Stripes()) { + return nil, nil, -1, fmt.Errorf("internal error: got %d stripe bytes, expected %d", len(striped), len(mkey)*int(keyslot.Stripes())) + } + stripes = append(stripes, striped) + } + keyslot.SetKeyMaterialOffset(uint32(headerLength / V1SectorSize)) + if err := h.SetKeySlot(i, keyslot); err != nil { + return nil, nil, -1, fmt.Errorf("internal error: setting value for key slot %d: %w", i, err) + } + headerLength += len(mkey) * int(keyslot.Stripes()) + headerLength = roundUpToMultiple(headerLength, V1AlignKeyslots) + } + headerLength = roundUpToMultiple(headerLength, V1SectorSize) + + h.SetPayloadOffset(uint32(headerLength / V1SectorSize)) + head := make([]byte, headerLength) + offset := copy(head, h[:]) + offset = roundUpToMultiple(offset, V1AlignKeyslots) + for _, stripe := range stripes { + copy(head[offset:], stripe) + offset = roundUpToMultiple(offset+len(stripe), V1AlignKeyslots) + } + ivTweak := 0 + encryptStream := func(plaintext []byte) ([]byte, error) { + ciphertext, err := v1encrypt(h.CipherName(), h.CipherMode(), ivTweak, mkey, plaintext, V1SectorSize, true) + ivTweak += len(plaintext) / V1SectorSize + return ciphertext, err + } + return head, encryptStream, V1SectorSize, nil +} + +// EncryptV2 prepares to encrypt data using one or more passwords and the +// specified cipher (or a default, if the specified cipher is ""). +// +// Returns a fixed LUKSv2 header which contains keying information, a +// function which will encrypt blocks of data in succession, and the size of +// chunks of data that it expects. +func EncryptV2(password []string, cipher string, payloadSectorSize int) ([]byte, func([]byte) ([]byte, error), int, error) { + if len(password) == 0 { + return nil, nil, -1, errors.New("at least one password is required") + } + if cipher == "" { + cipher = "aes-xts-plain64" + } + cipherSpec := strings.SplitN(cipher, "-", 3) + if len(cipherSpec) != 3 || len(cipherSpec[0]) == 0 || len(cipherSpec[1]) == 0 || len(cipherSpec[2]) == 0 { + return nil, nil, -1, fmt.Errorf("invalid cipher %q", cipher) + } + if payloadSectorSize == 0 { + payloadSectorSize = V2SectorSize + } + switch payloadSectorSize { + default: + return nil, nil, -1, fmt.Errorf("invalid sector size %d", payloadSectorSize) + case 512, 1024, 2048, 4096: + } + + headerSalts := make([]byte, v1SaltSize*3) + n, err := rand.Read(headerSalts) + if err != nil { + return nil, nil, -1, err + } + if n != len(headerSalts) { + return nil, nil, -1, errors.New("short read") + } + hSalt1 := headerSalts[:v1SaltSize] + hSalt2 := headerSalts[v1SaltSize : v1SaltSize*2] + mkeySalt := headerSalts[v1SaltSize*2:] + + roundHeaderSize := func(size int) (int, error) { + switch { + case size < 0x4000: + return 0x4000, nil + case size < 0x8000: + return 0x8000, nil + case size < 0x10000: + return 0x10000, nil + case size < 0x20000: + return 0x20000, nil + case size < 0x40000: + return 0x40000, nil + case size < 0x80000: + return 0x80000, nil + case size < 0x100000: + return 0x100000, nil + case size < 0x200000: + return 0x200000, nil + case size < 0x400000: + return 0x400000, nil + } + return 0, fmt.Errorf("internal error: unsupported header size %d", size) + } + + var h1, h2 V2Header + if err := h1.SetMagic(V2Magic1); err != nil { + return nil, nil, -1, fmt.Errorf("setting magic to v2: %w", err) + } + if err := h2.SetMagic(V2Magic2); err != nil { + return nil, nil, -1, fmt.Errorf("setting magic to v2: %w", err) + } + if err := h1.SetVersion(2); err != nil { + return nil, nil, -1, fmt.Errorf("setting version to 2: %w", err) + } + if err := h2.SetVersion(2); err != nil { + return nil, nil, -1, fmt.Errorf("setting version to 2: %w", err) + } + h1.SetSequenceID(1) + h2.SetSequenceID(1) + h1.SetLabel("") + h2.SetLabel("") + h1.SetChecksumAlgorithm("sha256") + h2.SetChecksumAlgorithm("sha256") + h1.SetSalt(hSalt1) + h2.SetSalt(hSalt2) + uuidString := uuid.NewString() + h1.SetUUID(uuidString) + h2.SetUUID(uuidString) + h1.SetHeaderOffset(0) + h2.SetHeaderOffset(0) + h1.SetChecksum(nil) + h2.SetChecksum(nil) + + mkey := make([]byte, 32) + if cipherSpec[1] == "xts" { + mkey = make([]byte, 64) + } + n, err = rand.Read(mkey) + if err != nil { + return nil, nil, -1, fmt.Errorf("reading random data: %w", err) + } + if n != len(mkey) { + return nil, nil, -1, errors.New("short read") + } + + tuningSalt := make([]byte, v1SaltSize) + hasher, err := hasherByName(h1.ChecksumAlgorithm()) + if err != nil { + return nil, nil, -1, errors.New("internal error") + } + iterations := IterationsPBKDF2(tuningSalt, len(mkey), hasher) + timeCost := 1 + threadsCost := 4 + memoryCost := MemoryCostArgon2(tuningSalt, len(mkey), timeCost, threadsCost) + priority := V2JSONKeyslotPriorityNormal + var stripes [][]byte + var keyslots []V2JSONKeyslot + + mdigest := pbkdf2.Key(mkey, mkeySalt, iterations, len(hasher().Sum([]byte{})), hasher) + digest0 := V2JSONDigest{ + Type: "pbkdf2", + Salt: mkeySalt, + Digest: mdigest, + Segments: []string{"0"}, + V2JSONDigestPbkdf2: &V2JSONDigestPbkdf2{ + Hash: h1.ChecksumAlgorithm(), + Iterations: iterations, + }, + } + + for i := range password { + keyslotSalt := make([]byte, v1SaltSize) + n, err := rand.Read(keyslotSalt) + if err != nil { + return nil, nil, -1, err + } + if n != len(keyslotSalt) { + return nil, nil, -1, errors.New("short read") + } + key := argon2.Key([]byte(password[i]), keyslotSalt, uint32(timeCost), uint32(memoryCost), uint8(threadsCost), uint32(len(mkey))) + split, err := afSplit(mkey, hasher(), V2Stripes) + if err != nil { + return nil, nil, -1, fmt.Errorf("splitting: %w", err) + } + striped, err := v2encrypt(cipher, 0, key, split, V1SectorSize, false) + if err != nil { + return nil, nil, -1, fmt.Errorf("encrypting: %w", err) + } + stripes = append(stripes, striped) + keyslot := V2JSONKeyslot{ + Type: "luks2", + KeySize: len(mkey), + Area: V2JSONArea{ + Type: "raw", + Offset: 10000000, // gets updated later + Size: int64(roundUpToMultiple(len(striped), V2AlignKeyslots)), + V2JSONAreaRaw: &V2JSONAreaRaw{ + Encryption: cipher, + KeySize: len(key), + }, + }, + Priority: &priority, + V2JSONKeyslotLUKS2: &V2JSONKeyslotLUKS2{ + AF: V2JSONAF{ + Type: "luks1", + V2JSONAFLUKS1: &V2JSONAFLUKS1{ + Stripes: V2Stripes, + Hash: h1.ChecksumAlgorithm(), + }, + }, + Kdf: V2JSONKdf{ + Type: "argon2i", + Salt: keyslotSalt, + V2JSONKdfArgon2i: &V2JSONKdfArgon2i{ + Time: timeCost, + Memory: memoryCost, + CPUs: threadsCost, + }, + }, + }, + } + keyslots = append(keyslots, keyslot) + digest0.Keyslots = append(digest0.Keyslots, strconv.Itoa(i)) + } + + segment0 := V2JSONSegment{ + Type: "crypt", + Offset: "10000000", // gets updated later + Size: "dynamic", + V2JSONSegmentCrypt: &V2JSONSegmentCrypt{ + IVTweak: 0, + Encryption: cipher, + SectorSize: payloadSectorSize, + }, + } + + j := V2JSON{ + Config: V2JSONConfig{}, + Keyslots: map[string]V2JSONKeyslot{}, + Digests: map[string]V2JSONDigest{}, + Segments: map[string]V2JSONSegment{}, + Tokens: map[string]V2JSONToken{}, + } +rebuild: + j.Digests["0"] = digest0 + j.Segments["0"] = segment0 + encodedJSON, err := json.Marshal(j) + if err != nil { + return nil, nil, -1, err + } + headerPlusPaddedJsonSize, err := roundHeaderSize(int(V2SectorSize) /* binary header */ + len(encodedJSON) + 1) + if err != nil { + return nil, nil, -1, err + } + if j.Config.JsonSize != headerPlusPaddedJsonSize-V2SectorSize { + j.Config.JsonSize = headerPlusPaddedJsonSize - V2SectorSize + goto rebuild + } + + if h1.HeaderSize() != uint64(headerPlusPaddedJsonSize) { + h1.SetHeaderSize(uint64(headerPlusPaddedJsonSize)) + h2.SetHeaderSize(uint64(headerPlusPaddedJsonSize)) + h1.SetHeaderOffset(0) + h2.SetHeaderOffset(uint64(headerPlusPaddedJsonSize)) + goto rebuild + } + + keyslotsOffset := h2.HeaderOffset() * 2 + maxKeys := len(password) + if maxKeys < 64 { + maxKeys = 64 + } + for i := 0; i < len(password); i++ { + oldOffset := keyslots[i].Area.Offset + keyslots[i].Area.Offset = int64(keyslotsOffset) + int64(roundUpToMultiple(len(mkey)*V2Stripes, V2AlignKeyslots))*int64(i) + j.Keyslots[strconv.Itoa(i)] = keyslots[i] + if keyslots[i].Area.Offset != oldOffset { + goto rebuild + } + } + keyslotsSize := roundUpToMultiple(len(mkey)*V2Stripes, V2AlignKeyslots) * maxKeys + if j.Config.KeyslotsSize != keyslotsSize { + j.Config.KeyslotsSize = keyslotsSize + goto rebuild + } + + segmentOffsetInt := roundUpToMultiple(int(keyslotsOffset)+j.Config.KeyslotsSize, V2SectorSize) + segmentOffset := strconv.Itoa(segmentOffsetInt) + if segment0.Offset != segmentOffset { + segment0.Offset = segmentOffset + goto rebuild + } + + d1 := hasher() + h1.SetChecksum(nil) + d1.Write(h1[:]) + d1.Write(encodedJSON) + zeropad := make([]byte, headerPlusPaddedJsonSize-len(h1)-len(encodedJSON)) + d1.Write(zeropad) + h1.SetChecksum(d1.Sum(nil)) + d2 := hasher() + h2.SetChecksum(nil) + d2.Write(h2[:]) + d2.Write(encodedJSON) + d1.Write(zeropad) + h2.SetChecksum(d2.Sum(nil)) + + head := make([]byte, segmentOffsetInt) + copy(head, h1[:]) + copy(head[V2SectorSize:], encodedJSON) + copy(head[h2.HeaderOffset():], h2[:]) + copy(head[h2.HeaderOffset()+V2SectorSize:], encodedJSON) + for i := 0; i < len(password); i++ { + iAsString := strconv.Itoa(i) + copy(head[j.Keyslots[iAsString].Area.Offset:], stripes[i]) + } + ivTweak := 0 + encryptStream := func(plaintext []byte) ([]byte, error) { + ciphertext, err := v2encrypt(cipher, ivTweak, mkey, plaintext, payloadSectorSize, true) + ivTweak += len(plaintext) / payloadSectorSize + return ciphertext, err + } + return head, encryptStream, segment0.SectorSize, nil +} diff --git a/vendor/github.com/containers/luksy/encryption.go b/vendor/github.com/containers/luksy/encryption.go new file mode 100644 index 00000000000..bd08cc8222f --- /dev/null +++ b/vendor/github.com/containers/luksy/encryption.go @@ -0,0 +1,537 @@ +package luksy + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/binary" + "errors" + "fmt" + "hash" + "io" + "strings" + + "github.com/aead/serpent" + "golang.org/x/crypto/cast5" + "golang.org/x/crypto/ripemd160" + "golang.org/x/crypto/twofish" + "golang.org/x/crypto/xts" +) + +func v1encrypt(cipherName, cipherMode string, ivTweak int, key []byte, plaintext []byte, sectorSize int, bulk bool) ([]byte, error) { + var err error + var newBlockCipher func([]byte) (cipher.Block, error) + ciphertext := make([]byte, len(plaintext)) + + switch cipherName { + case "aes": + newBlockCipher = aes.NewCipher + case "twofish": + newBlockCipher = func(key []byte) (cipher.Block, error) { return twofish.NewCipher(key) } + case "cast5": + newBlockCipher = func(key []byte) (cipher.Block, error) { return cast5.NewCipher(key) } + case "serpent": + newBlockCipher = serpent.NewCipher + default: + return nil, fmt.Errorf("unsupported cipher %s", cipherName) + } + if sectorSize == 0 { + sectorSize = V1SectorSize + } + switch sectorSize { + default: + return nil, fmt.Errorf("invalid sector size %d", sectorSize) + case 512, 1024, 2048, 4096: + } + + switch cipherMode { + case "ecb": + cipher, err := newBlockCipher(key) + if err != nil { + return nil, fmt.Errorf("initializing encryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += cipher.BlockSize() { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + cipher.Encrypt(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft]) + } + case "cbc-plain": + block, err := newBlockCipher(key) + if err != nil { + return nil, fmt.Errorf("initializing encryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + ivValue := processed/sectorSize + ivTweak + if bulk { // iv_large_sectors is not being used + ivValue *= sectorSize / V1SectorSize + } + iv0 := make([]byte, block.BlockSize()) + binary.LittleEndian.PutUint32(iv0, uint32(ivValue)) + cipher := cipher.NewCBCEncrypter(block, iv0) + cipher.CryptBlocks(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft]) + } + case "cbc-plain64": + block, err := newBlockCipher(key) + if err != nil { + return nil, fmt.Errorf("initializing encryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + ivValue := processed/sectorSize + ivTweak + if bulk { // iv_large_sectors is not being used + ivValue *= sectorSize / V1SectorSize + } + iv0 := make([]byte, block.BlockSize()) + binary.LittleEndian.PutUint64(iv0, uint64(ivValue)) + cipher := cipher.NewCBCEncrypter(block, iv0) + cipher.CryptBlocks(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft]) + } + case "cbc-essiv:sha256": + hasherName := strings.TrimPrefix(cipherMode, "cbc-essiv:") + hasher, err := hasherByName(hasherName) + if err != nil { + return nil, fmt.Errorf("initializing encryption using hash %s: %w", hasherName, err) + } + h := hasher() + h.Write(key) + makeiv, err := newBlockCipher(h.Sum(nil)) + if err != nil { + return nil, fmt.Errorf("initializing encryption: %w", err) + } + block, err := newBlockCipher(key) + if err != nil { + return nil, fmt.Errorf("initializing encryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + ivValue := (processed/sectorSize + ivTweak) + if bulk { // iv_large_sectors is not being used + ivValue *= sectorSize / V1SectorSize + } + plain0 := make([]byte, makeiv.BlockSize()) + binary.LittleEndian.PutUint64(plain0, uint64(ivValue)) + iv0 := make([]byte, makeiv.BlockSize()) + makeiv.Encrypt(iv0, plain0) + cipher := cipher.NewCBCEncrypter(block, iv0) + cipher.CryptBlocks(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft]) + } + case "xts-plain": + cipher, err := xts.NewCipher(newBlockCipher, key) + if err != nil { + return nil, fmt.Errorf("initializing encryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + sector := uint64(processed/sectorSize + ivTweak) + if bulk { // iv_large_sectors is not being used + sector *= uint64(sectorSize / V1SectorSize) + } + sector = sector % 0x100000000 + cipher.Encrypt(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft], sector) + } + case "xts-plain64": + cipher, err := xts.NewCipher(newBlockCipher, key) + if err != nil { + return nil, fmt.Errorf("initializing encryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + sector := uint64(processed/sectorSize + ivTweak) + if bulk { // iv_large_sectors is not being used + sector *= uint64(sectorSize / V1SectorSize) + } + cipher.Encrypt(ciphertext[processed:processed+blockLeft], plaintext[processed:processed+blockLeft], sector) + } + default: + return nil, fmt.Errorf("unsupported cipher mode %s", cipherMode) + } + + if err != nil { + return nil, fmt.Errorf("cipher error: %w", err) + } + return ciphertext, nil +} + +func v1decrypt(cipherName, cipherMode string, ivTweak int, key []byte, ciphertext []byte, sectorSize int, bulk bool) ([]byte, error) { + var err error + var newBlockCipher func([]byte) (cipher.Block, error) + plaintext := make([]byte, len(ciphertext)) + + switch cipherName { + case "aes": + newBlockCipher = aes.NewCipher + case "twofish": + newBlockCipher = func(key []byte) (cipher.Block, error) { return twofish.NewCipher(key) } + case "cast5": + newBlockCipher = func(key []byte) (cipher.Block, error) { return cast5.NewCipher(key) } + case "serpent": + newBlockCipher = serpent.NewCipher + default: + return nil, fmt.Errorf("unsupported cipher %s", cipherName) + } + if sectorSize == 0 { + sectorSize = V1SectorSize + } + switch sectorSize { + default: + return nil, fmt.Errorf("invalid sector size %d", sectorSize) + case 512, 1024, 2048, 4096: + } + + switch cipherMode { + case "ecb": + cipher, err := newBlockCipher(key) + if err != nil { + return nil, fmt.Errorf("initializing decryption: %w", err) + } + for processed := 0; processed < len(ciphertext); processed += cipher.BlockSize() { + blockLeft := sectorSize + if processed+blockLeft > len(ciphertext) { + blockLeft = len(ciphertext) - processed + } + cipher.Decrypt(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft]) + } + case "cbc-plain": + block, err := newBlockCipher(key) + if err != nil { + return nil, fmt.Errorf("initializing decryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + ivValue := processed/sectorSize + ivTweak + if bulk { // iv_large_sectors is not being used + ivValue *= sectorSize / V1SectorSize + } + iv0 := make([]byte, block.BlockSize()) + binary.LittleEndian.PutUint32(iv0, uint32(ivValue)) + cipher := cipher.NewCBCDecrypter(block, iv0) + cipher.CryptBlocks(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft]) + } + case "cbc-plain64": + block, err := newBlockCipher(key) + if err != nil { + return nil, fmt.Errorf("initializing decryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + ivValue := processed/sectorSize + ivTweak + if bulk { // iv_large_sectors is not being used + ivValue *= sectorSize / V1SectorSize + } + iv0 := make([]byte, block.BlockSize()) + binary.LittleEndian.PutUint64(iv0, uint64(ivValue)) + cipher := cipher.NewCBCDecrypter(block, iv0) + cipher.CryptBlocks(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft]) + } + case "cbc-essiv:sha256": + hasherName := strings.TrimPrefix(cipherMode, "cbc-essiv:") + hasher, err := hasherByName(hasherName) + if err != nil { + return nil, fmt.Errorf("initializing decryption using hash %s: %w", hasherName, err) + } + h := hasher() + h.Write(key) + makeiv, err := newBlockCipher(h.Sum(nil)) + if err != nil { + return nil, fmt.Errorf("initializing decryption: %w", err) + } + block, err := newBlockCipher(key) + if err != nil { + return nil, fmt.Errorf("initializing decryption: %w", err) + } + for processed := 0; processed < len(plaintext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(plaintext) { + blockLeft = len(plaintext) - processed + } + ivValue := (processed/sectorSize + ivTweak) + if bulk { // iv_large_sectors is not being used + ivValue *= sectorSize / V1SectorSize + } + plain0 := make([]byte, makeiv.BlockSize()) + binary.LittleEndian.PutUint64(plain0, uint64(ivValue)) + iv0 := make([]byte, makeiv.BlockSize()) + makeiv.Encrypt(iv0, plain0) + cipher := cipher.NewCBCDecrypter(block, iv0) + cipher.CryptBlocks(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft]) + } + case "xts-plain": + cipher, err := xts.NewCipher(newBlockCipher, key) + if err != nil { + return nil, fmt.Errorf("initializing decryption: %w", err) + } + for processed := 0; processed < len(ciphertext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(ciphertext) { + blockLeft = len(ciphertext) - processed + } + sector := uint64(processed/sectorSize + ivTweak) + if bulk { // iv_large_sectors is not being used + sector *= uint64(sectorSize / V1SectorSize) + } + sector = sector % 0x100000000 + cipher.Decrypt(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft], sector) + } + case "xts-plain64": + cipher, err := xts.NewCipher(newBlockCipher, key) + if err != nil { + return nil, fmt.Errorf("initializing decryption: %w", err) + } + for processed := 0; processed < len(ciphertext); processed += sectorSize { + blockLeft := sectorSize + if processed+blockLeft > len(ciphertext) { + blockLeft = len(ciphertext) - processed + } + sector := uint64(processed/sectorSize + ivTweak) + if bulk { // iv_large_sectors is not being used + sector *= uint64(sectorSize / V1SectorSize) + } + cipher.Decrypt(plaintext[processed:processed+blockLeft], ciphertext[processed:processed+blockLeft], sector) + } + default: + return nil, fmt.Errorf("unsupported cipher mode %s", cipherMode) + } + + if err != nil { + return nil, fmt.Errorf("cipher error: %w", err) + } + return plaintext, nil +} + +func v2encrypt(cipherSuite string, ivTweak int, key []byte, ciphertext []byte, sectorSize int, bulk bool) ([]byte, error) { + var cipherName, cipherMode string + switch { + default: + cipherSpec := strings.SplitN(cipherSuite, "-", 2) + if len(cipherSpec) < 2 { + return nil, fmt.Errorf("unrecognized cipher suite %q", cipherSuite) + } + cipherName = cipherSpec[0] + cipherMode = cipherSpec[1] + } + return v1encrypt(cipherName, cipherMode, ivTweak, key, ciphertext, sectorSize, bulk) +} + +func v2decrypt(cipherSuite string, ivTweak int, key []byte, ciphertext []byte, sectorSize int, bulk bool) ([]byte, error) { + var cipherName, cipherMode string + switch { + default: + cipherSpec := strings.SplitN(cipherSuite, "-", 2) + if len(cipherSpec) < 2 { + return nil, fmt.Errorf("unrecognized cipher suite %q", cipherSuite) + } + cipherName = cipherSpec[0] + cipherMode = cipherSpec[1] + } + return v1decrypt(cipherName, cipherMode, ivTweak, key, ciphertext, sectorSize, bulk) +} + +func diffuse(key []byte, h hash.Hash) []byte { + sum := make([]byte, len(key)) + counter := uint32(0) + for summed := 0; summed < len(key); summed += h.Size() { + h.Reset() + var buf [4]byte + binary.BigEndian.PutUint32(buf[:], counter) + h.Write(buf[:]) + needed := len(key) - summed + if needed > h.Size() { + needed = h.Size() + } + h.Write(key[summed : summed+needed]) + partial := h.Sum(nil) + copy(sum[summed:summed+needed], partial) + counter++ + } + return sum +} + +func afMerge(splitKey []byte, h hash.Hash, keysize int, stripes int) ([]byte, error) { + if len(splitKey) != keysize*stripes { + return nil, fmt.Errorf("expected %d af bytes, got %d", keysize*stripes, len(splitKey)) + } + d := make([]byte, keysize) + for i := 0; i < stripes-1; i++ { + for j := 0; j < keysize; j++ { + d[j] = d[j] ^ splitKey[i*keysize+j] + } + d = diffuse(d, h) + } + for j := 0; j < keysize; j++ { + d[j] = d[j] ^ splitKey[(stripes-1)*keysize+j] + } + return d, nil +} + +func afSplit(key []byte, h hash.Hash, stripes int) ([]byte, error) { + keysize := len(key) + s := make([]byte, keysize*stripes) + d := make([]byte, keysize) + n, err := rand.Read(s[0 : (keysize-1)*stripes]) + if err != nil { + return nil, err + } + if n != (keysize-1)*stripes { + return nil, fmt.Errorf("short read when attempting to read random data: %d < %d", n, (keysize-1)*stripes) + } + for i := 0; i < stripes-1; i++ { + for j := 0; j < keysize; j++ { + d[j] = d[j] ^ s[i*keysize+j] + } + d = diffuse(d, h) + } + for j := 0; j < keysize; j++ { + s[(stripes-1)*keysize+j] = d[j] ^ key[j] + } + return s, nil +} + +func roundUpToMultiple(i, factor int) int { + if i < 0 { + return 0 + } + return i + ((factor - (i % factor)) % factor) +} + +func hasherByName(name string) (func() hash.Hash, error) { + switch name { + case "sha1": + return sha1.New, nil + case "sha256": + return sha256.New, nil + case "sha512": + return sha512.New, nil + case "ripemd160": + return ripemd160.New, nil + default: + return nil, fmt.Errorf("unsupported digest algorithm %q", name) + } +} + +type wrapper struct { + fn func(plaintext []byte) ([]byte, error) + blockSize int + buf []byte + buffered, consumed int + reader io.Reader + eof bool + writer io.Writer +} + +func (w *wrapper) Write(buf []byte) (int, error) { + n := 0 + for n < len(buf) { + nBuffered := copy(w.buf[w.buffered:], buf[n:]) + w.buffered += nBuffered + n += nBuffered + if w.buffered == len(w.buf) { + processed, err := w.fn(w.buf) + if err != nil { + return n, err + } + nWritten, err := w.writer.Write(processed) + if err != nil { + return n, err + } + w.buffered -= nWritten + if nWritten != len(processed) { + return n, fmt.Errorf("short write: %d != %d", nWritten, len(processed)) + } + } + } + return n, nil +} + +func (w *wrapper) Read(buf []byte) (int, error) { + n := 0 + for n < len(buf) { + nRead := copy(buf[n:], w.buf[w.consumed:]) + w.consumed += nRead + n += nRead + if w.consumed == len(w.buf) && !w.eof { + nRead, err := w.reader.Read(w.buf) + w.eof = errors.Is(err, io.EOF) + if err != nil && !w.eof { + return n, err + } + if nRead != len(w.buf) && !w.eof { + return n, fmt.Errorf("short read: %d != %d", nRead, len(w.buf)) + } + processed, err := w.fn(w.buf[:nRead]) + if err != nil { + return n, err + } + w.buf = processed + w.consumed = 0 + } + } + var eof error + if w.consumed == len(w.buf) && w.eof { + eof = io.EOF + } + return n, eof +} + +func (w *wrapper) Close() error { + if w.writer != nil { + if w.buffered%w.blockSize != 0 { + w.buffered += copy(w.buf[w.buffered:], make([]byte, roundUpToMultiple(w.buffered%w.blockSize, w.blockSize))) + } + processed, err := w.fn(w.buf[:w.buffered]) + if err != nil { + return err + } + nWritten, err := w.writer.Write(processed) + if err != nil { + return err + } + if nWritten != len(processed) { + return fmt.Errorf("short write: %d != %d", nWritten, len(processed)) + } + w.buffered = 0 + } + return nil +} + +// EncryptWriter creates an io.WriteCloser which buffers writes through an +// encryption function. After writing a final block, the returned writer +// should be closed. +func EncryptWriter(fn func(plaintext []byte) ([]byte, error), writer io.Writer, blockSize int) io.WriteCloser { + bufferSize := roundUpToMultiple(1024*1024, blockSize) + return &wrapper{fn: fn, blockSize: blockSize, buf: make([]byte, bufferSize), writer: writer} +} + +// DecryptReader creates an io.ReadCloser which buffers reads through a +// decryption function. When data will no longer be read, the returned reader +// should be closed. +func DecryptReader(fn func(ciphertext []byte) ([]byte, error), reader io.Reader, blockSize int) io.ReadCloser { + bufferSize := roundUpToMultiple(1024*1024, blockSize) + return &wrapper{fn: fn, blockSize: blockSize, buf: make([]byte, bufferSize), consumed: bufferSize, reader: reader} +} diff --git a/vendor/github.com/containers/luksy/luks.go b/vendor/github.com/containers/luksy/luks.go new file mode 100644 index 00000000000..a0c95277cba --- /dev/null +++ b/vendor/github.com/containers/luksy/luks.go @@ -0,0 +1,75 @@ +package luksy + +import ( + "bytes" + "encoding/json" + "fmt" + "os" +) + +// ReadHeaderOptions can control some of what ReadHeaders() does. +type ReadHeaderOptions struct{} + +// ReadHeaders reads LUKS headers from the specified file, returning either a +// LUKSv1 header, or two LUKSv2 headers and a LUKSv2 JSON block, depending on +// which format is detected. +func ReadHeaders(f *os.File, options ReadHeaderOptions) (*V1Header, *V2Header, *V2Header, *V2JSON, error) { + var v1 V1Header + var v2a, v2b V2Header + n, err := f.ReadAt(v2a[:], 0) + if err != nil { + return nil, nil, nil, nil, err + } + if n != len(v2a) { + return nil, nil, nil, nil, fmt.Errorf("only able to read %d bytes - file truncated?", n) + } + if n, err = f.ReadAt(v1[:], 0); err != nil { + return nil, nil, nil, nil, err + } + if n != len(v1) { + return nil, nil, nil, nil, fmt.Errorf("only able to read %d bytes - file truncated?", n) + } + if v2a.Magic() != V2Magic1 { + return nil, nil, nil, nil, fmt.Errorf("internal error: magic mismatch in LUKS header (%q)", v2a.Magic()) + } + switch v2a.Version() { // is it a v1 header, or the first v2 header? + case 1: + return &v1, nil, nil, nil, nil + case 2: + size := v2a.HeaderSize() + if size > 0x7fffffffffffffff { + return nil, nil, nil, nil, fmt.Errorf("unsupported header size while looking for second header") + } + if size < 4096 { + return nil, nil, nil, nil, fmt.Errorf("unsupported header size while looking for JSON data") + } + if n, err = f.ReadAt(v2b[:], int64(size)); err != nil || n != len(v2b) { + if err == nil && n != len(v2b) { + err = fmt.Errorf("short read: read only %d bytes, should have read %d", n, len(v2b)) + } + return nil, nil, nil, nil, err + } + if v2b.Magic() != V2Magic2 { + return nil, nil, nil, nil, fmt.Errorf("internal error: magic mismatch in second LUKS header (%q)", v2b.Magic()) + } + jsonSize := size - 4096 + buf := make([]byte, jsonSize) + n, err = f.ReadAt(buf[:], 4096) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("internal error: while reading JSON data: %w", err) + } + if n < 0 || uint64(n) != jsonSize { + return nil, nil, nil, nil, fmt.Errorf("internal error: short read while reading JSON data (wanted %d, got %d)", jsonSize, n) + } + var jsonData V2JSON + buf = bytes.TrimRightFunc(buf, func(r rune) bool { return r == 0 }) + if err = json.Unmarshal(buf, &jsonData); err != nil { + return nil, nil, nil, nil, fmt.Errorf("internal error: decoding JSON data: %w", err) + } + if uint64(jsonData.Config.JsonSize) != jsonSize { + return nil, nil, nil, nil, fmt.Errorf("internal error: JSON data size mismatch: (expected %d, used %d)", jsonData.Config.JsonSize, jsonSize) + } + return nil, &v2a, &v2b, &jsonData, nil + } + return nil, nil, nil, nil, fmt.Errorf("error reading LUKS header - magic identifier not found") +} diff --git a/vendor/github.com/containers/luksy/tune.go b/vendor/github.com/containers/luksy/tune.go new file mode 100644 index 00000000000..ac01cf105d4 --- /dev/null +++ b/vendor/github.com/containers/luksy/tune.go @@ -0,0 +1,55 @@ +package luksy + +import ( + "hash" + "time" + + "golang.org/x/crypto/argon2" + "golang.org/x/crypto/pbkdf2" +) + +func durationOf(f func()) time.Duration { + start := time.Now() + f() + return time.Since(start) +} + +func IterationsPBKDF2(salt []byte, keyLen int, h func() hash.Hash) int { + iterations := 2 + var d time.Duration + for d < time.Second { + d = durationOf(func() { + _ = pbkdf2.Key([]byte{}, salt, iterations, keyLen, h) + }) + if d < time.Second/10 { + iterations *= 2 + } else { + return iterations * int(time.Second) / int(d) + } + } + return iterations +} + +func memoryCostArgon2(salt []byte, keyLen, timeCost, threadsCost int, kdf func([]byte, []byte, uint32, uint32, uint8, uint32) []byte) int { + memoryCost := 2 + var d time.Duration + for d < time.Second { + d = durationOf(func() { + _ = kdf([]byte{}, salt, uint32(timeCost), uint32(memoryCost), uint8(threadsCost), uint32(keyLen)) + }) + if d < time.Second/10 { + memoryCost *= 2 + } else { + return memoryCost * int(time.Second) / int(d) + } + } + return memoryCost +} + +func MemoryCostArgon2(salt []byte, keyLen, timeCost, threadsCost int) int { + return memoryCostArgon2(salt, keyLen, timeCost, threadsCost, argon2.Key) +} + +func MemoryCostArgon2i(salt []byte, keyLen, timeCost, threadsCost int) int { + return memoryCostArgon2(salt, keyLen, timeCost, threadsCost, argon2.IDKey) +} diff --git a/vendor/github.com/containers/luksy/v1header.go b/vendor/github.com/containers/luksy/v1header.go new file mode 100644 index 00000000000..ded4a61db9c --- /dev/null +++ b/vendor/github.com/containers/luksy/v1header.go @@ -0,0 +1,321 @@ +package luksy + +import ( + "encoding/binary" + "fmt" + "syscall" +) + +type ( + V1Header [592]uint8 + V1KeySlot [48]uint8 +) + +const ( + // Mostly verbatim from LUKS1 On-Disk Format Specification version 1.2.3 + V1Magic = "LUKS\xba\xbe" + v1MagicStart = 0 + v1MagicLength = 6 + v1VersionStart = v1MagicStart + v1MagicLength + v1VersionLength = 2 + v1CipherNameStart = v1VersionStart + v1VersionLength + v1CipherNameLength = 32 + v1CipherModeStart = v1CipherNameStart + v1CipherNameLength + v1CipherModeLength = 32 + v1HashSpecStart = v1CipherModeStart + v1CipherModeLength + v1HashSpecLength = 32 + v1PayloadOffsetStart = v1HashSpecStart + v1HashSpecLength + v1PayloadOffsetLength = 4 + v1KeyBytesStart = v1PayloadOffsetStart + v1PayloadOffsetLength + v1KeyBytesLength = 4 + v1MKDigestStart = v1KeyBytesStart + v1KeyBytesLength + v1MKDigestLength = v1DigestSize + v1MKDigestSaltStart = v1MKDigestStart + v1MKDigestLength + v1MKDigestSaltLength = v1SaltSize + v1MKDigestIterStart = v1MKDigestSaltStart + v1MKDigestSaltLength + v1MKDigestIterLength = 4 + v1UUIDStart = v1MKDigestIterStart + v1MKDigestIterLength + v1UUIDLength = 40 + v1KeySlot1Start = v1UUIDStart + v1UUIDLength + v1KeySlot1Length = 48 + v1KeySlot2Start = v1KeySlot1Start + v1KeySlot1Length + v1KeySlot2Length = 48 + v1KeySlot3Start = v1KeySlot2Start + v1KeySlot2Length + v1KeySlot3Length = 48 + v1KeySlot4Start = v1KeySlot3Start + v1KeySlot3Length + v1KeySlot4Length = 48 + v1KeySlot5Start = v1KeySlot4Start + v1KeySlot4Length + v1KeySlot5Length = 48 + v1KeySlot6Start = v1KeySlot5Start + v1KeySlot5Length + v1KeySlot6Length = 48 + v1KeySlot7Start = v1KeySlot6Start + v1KeySlot6Length + v1KeySlot7Length = 48 + v1KeySlot8Start = v1KeySlot7Start + v1KeySlot7Length + v1KeySlot8Length = 48 + v1HeaderStructSize = v1KeySlot8Start + v1KeySlot8Length + + v1KeySlotActiveStart = 0 + v1KeySlotActiveLength = 4 + v1KeySlotIterationsStart = v1KeySlotActiveStart + v1KeySlotActiveLength + v1KeySlotIterationsLength = 4 + v1KeySlotSaltStart = v1KeySlotIterationsStart + v1KeySlotIterationsLength + v1KeySlotSaltLength = v1SaltSize + v1KeySlotKeyMaterialOffsetStart = v1KeySlotSaltStart + v1KeySlotSaltLength + v1KeySlotKeyMaterialOffsetLength = 4 + v1KeySlotStripesStart = v1KeySlotKeyMaterialOffsetStart + v1KeySlotKeyMaterialOffsetLength + v1KeySlotStripesLength = 4 + v1KeySlotStructSize = v1KeySlotStripesStart + v1KeySlotStripesLength + + v1DigestSize = 20 + v1SaltSize = 32 + v1NumKeys = 8 + v1KeySlotActiveKeyDisabled = 0x0000dead + v1KeySlotActiveKeyEnabled = 0x00ac71f3 + V1Stripes = 4000 + V1AlignKeyslots = 4096 + V1SectorSize = 512 +) + +func (h V1Header) readu2(offset int) uint16 { + return binary.BigEndian.Uint16(h[offset:]) +} + +func (h V1Header) readu4(offset int) uint32 { + return binary.BigEndian.Uint32(h[offset:]) +} + +func (h *V1Header) writeu2(offset int, value uint16) { + binary.BigEndian.PutUint16(h[offset:], value) +} + +func (h *V1Header) writeu4(offset int, value uint32) { + binary.BigEndian.PutUint32(h[offset:], value) +} + +func (h V1Header) Magic() string { + return trimZeroPad(string(h[v1MagicStart : v1MagicStart+v1MagicLength])) +} + +func (h *V1Header) SetMagic(magic string) error { + switch magic { + case V1Magic: + copy(h[v1MagicStart:v1MagicStart+v1MagicLength], []uint8(magic)) + return nil + } + return fmt.Errorf("magic %q not acceptable, only %q is an acceptable magic value: %w", magic, V1Magic, syscall.EINVAL) +} + +func (h V1Header) Version() uint16 { + return h.readu2(v1VersionStart) +} + +func (h *V1Header) SetVersion(version uint16) error { + switch version { + case 1: + h.writeu2(v1VersionStart, version) + return nil + } + return fmt.Errorf("version %d not acceptable, only 1 is an acceptable version: %w", version, syscall.EINVAL) +} + +func (h *V1Header) setZeroString(offset int, value string, length int) { + for len(value) < length { + value = value + "\000" + } + copy(h[offset:offset+length], []uint8(value)) +} + +func (h *V1Header) setInt8(offset int, s []uint8, length int) { + t := make([]byte, length) + copy(t, s) + copy(h[offset:offset+length], s) +} + +func (h V1Header) CipherName() string { + return trimZeroPad(string(h[v1CipherNameStart : v1CipherNameStart+v1CipherNameLength])) +} + +func (h *V1Header) SetCipherName(name string) { + h.setZeroString(v1CipherNameStart, name, v1CipherNameLength) +} + +func (h V1Header) CipherMode() string { + return trimZeroPad(string(h[v1CipherModeStart : v1CipherModeStart+v1CipherModeLength])) +} + +func (h *V1Header) SetCipherMode(mode string) { + h.setZeroString(v1CipherModeStart, mode, v1CipherModeLength) +} + +func (h V1Header) HashSpec() string { + return trimZeroPad(string(h[v1HashSpecStart : v1HashSpecStart+v1HashSpecLength])) +} + +func (h *V1Header) SetHashSpec(spec string) { + h.setZeroString(v1HashSpecStart, spec, v1HashSpecLength) +} + +func (h V1Header) PayloadOffset() uint32 { + return h.readu4(v1PayloadOffsetStart) +} + +func (h *V1Header) SetPayloadOffset(offset uint32) { + h.writeu4(v1PayloadOffsetStart, offset) +} + +func (h V1Header) KeyBytes() uint32 { + return h.readu4(v1KeyBytesStart) +} + +func (h *V1Header) SetKeyBytes(bytes uint32) { + h.writeu4(v1KeyBytesStart, bytes) +} + +func (h *V1Header) KeySlot(slot int) (V1KeySlot, error) { + var ks V1KeySlot + if slot < 0 || slot >= v1NumKeys { + return ks, fmt.Errorf("invalid key slot number (must be 0..%d)", v1NumKeys-1) + } + switch slot { + case 0: + copy(ks[:], h[v1KeySlot1Start:v1KeySlot1Start+v1KeySlot1Length]) + case 1: + copy(ks[:], h[v1KeySlot2Start:v1KeySlot2Start+v1KeySlot2Length]) + case 2: + copy(ks[:], h[v1KeySlot3Start:v1KeySlot3Start+v1KeySlot3Length]) + case 3: + copy(ks[:], h[v1KeySlot4Start:v1KeySlot4Start+v1KeySlot4Length]) + case 4: + copy(ks[:], h[v1KeySlot5Start:v1KeySlot5Start+v1KeySlot5Length]) + case 5: + copy(ks[:], h[v1KeySlot6Start:v1KeySlot6Start+v1KeySlot6Length]) + case 6: + copy(ks[:], h[v1KeySlot7Start:v1KeySlot7Start+v1KeySlot7Length]) + case 7: + copy(ks[:], h[v1KeySlot8Start:v1KeySlot8Start+v1KeySlot8Length]) + } + return ks, nil +} + +func (h *V1Header) SetKeySlot(slot int, ks V1KeySlot) error { + if slot < 0 || slot >= v1NumKeys { + return fmt.Errorf("invalid key slot number (must be 0..%d)", v1NumKeys-1) + } + switch slot { + case 0: + copy(h[v1KeySlot1Start:v1KeySlot1Start+v1KeySlot1Length], ks[:]) + case 1: + copy(h[v1KeySlot2Start:v1KeySlot2Start+v1KeySlot2Length], ks[:]) + case 2: + copy(h[v1KeySlot3Start:v1KeySlot3Start+v1KeySlot3Length], ks[:]) + case 3: + copy(h[v1KeySlot4Start:v1KeySlot4Start+v1KeySlot4Length], ks[:]) + case 4: + copy(h[v1KeySlot5Start:v1KeySlot5Start+v1KeySlot5Length], ks[:]) + case 5: + copy(h[v1KeySlot6Start:v1KeySlot6Start+v1KeySlot6Length], ks[:]) + case 6: + copy(h[v1KeySlot7Start:v1KeySlot7Start+v1KeySlot7Length], ks[:]) + case 7: + copy(h[v1KeySlot8Start:v1KeySlot8Start+v1KeySlot8Length], ks[:]) + } + return nil +} + +func (h V1Header) MKDigest() []uint8 { + return dupInt8(h[v1MKDigestStart : v1MKDigestStart+v1MKDigestLength]) +} + +func (h *V1Header) SetMKDigest(digest []uint8) { + h.setInt8(v1MKDigestStart, digest, v1MKDigestLength) +} + +func (h V1Header) MKDigestSalt() []uint8 { + return dupInt8(h[v1MKDigestSaltStart : v1MKDigestSaltStart+v1MKDigestSaltLength]) +} + +func (h *V1Header) SetMKDigestSalt(salt []uint8) { + h.setInt8(v1MKDigestSaltStart, salt, v1MKDigestSaltLength) +} + +func (h V1Header) MKDigestIter() uint32 { + return h.readu4(v1MKDigestIterStart) +} + +func (h *V1Header) SetMKDigestIter(bytes uint32) { + h.writeu4(v1MKDigestIterStart, bytes) +} + +func (h V1Header) UUID() string { + return trimZeroPad(string(h[v1UUIDStart : v1UUIDStart+v1UUIDLength])) +} + +func (h *V1Header) SetUUID(uuid string) { + h.setZeroString(v1UUIDStart, uuid, v1UUIDLength) +} + +func (s V1KeySlot) readu4(offset int) uint32 { + return binary.BigEndian.Uint32(s[offset:]) +} + +func (s *V1KeySlot) writeu4(offset int, value uint32) { + binary.BigEndian.PutUint32(s[offset:], value) +} + +func (s *V1KeySlot) setInt8(offset int, i []uint8, length int) { + for len(s) < length { + i = append(i, 0) + } + copy(s[offset:offset+length], i) +} + +func (s V1KeySlot) Active() (bool, error) { + active := s.readu4(v1KeySlotActiveStart) + switch active { + case v1KeySlotActiveKeyDisabled: + return false, nil + case v1KeySlotActiveKeyEnabled: + return true, nil + } + return false, fmt.Errorf("got invalid active value %#0x: %w", active, syscall.EINVAL) +} + +func (s *V1KeySlot) SetActive(active bool) { + if active { + s.writeu4(v1KeySlotActiveStart, v1KeySlotActiveKeyEnabled) + return + } + s.writeu4(v1KeySlotActiveStart, v1KeySlotActiveKeyDisabled) +} + +func (s V1KeySlot) Iterations() uint32 { + return s.readu4(v1KeySlotIterationsStart) +} + +func (s *V1KeySlot) SetIterations(iterations uint32) { + s.writeu4(v1KeySlotIterationsStart, iterations) +} + +func (s V1KeySlot) KeySlotSalt() []uint8 { + return dupInt8(s[v1KeySlotSaltStart : v1KeySlotSaltStart+v1KeySlotSaltLength]) +} + +func (s *V1KeySlot) SetKeySlotSalt(salt []uint8) { + s.setInt8(v1KeySlotSaltStart, salt, v1KeySlotSaltLength) +} + +func (s V1KeySlot) KeyMaterialOffset() uint32 { + return s.readu4(v1KeySlotKeyMaterialOffsetStart) +} + +func (s *V1KeySlot) SetKeyMaterialOffset(material uint32) { + s.writeu4(v1KeySlotKeyMaterialOffsetStart, material) +} + +func (s V1KeySlot) Stripes() uint32 { + return s.readu4(v1KeySlotStripesStart) +} + +func (s *V1KeySlot) SetStripes(stripes uint32) { + s.writeu4(v1KeySlotStripesStart, stripes) +} diff --git a/vendor/github.com/containers/luksy/v2header.go b/vendor/github.com/containers/luksy/v2header.go new file mode 100644 index 00000000000..4f94a05e6f3 --- /dev/null +++ b/vendor/github.com/containers/luksy/v2header.go @@ -0,0 +1,203 @@ +package luksy + +import ( + "fmt" + "strings" + "syscall" +) + +type V2Header [4096]uint8 + +const ( + // Mostly verbatim from LUKS2 On-Disk Format Specification version 1.1.1 + V2Magic1 = V1Magic + V2Magic2 = "SKUL\xba\xbe" + v2MagicStart = 0 + v2MagicLength = 6 + v2VersionStart = v2MagicStart + v2MagicLength + v2VersionLength = 2 + v2HeaderSizeStart = v2VersionStart + v2VersionLength + v2HeaderSizeLength = 8 + v2SequenceIDStart = v2HeaderSizeStart + v2HeaderSizeLength + v2SequenceIDLength = 8 + v2LabelStart = v2SequenceIDStart + v2SequenceIDLength + v2LabelLength = 48 + v2ChecksumAlgorithmStart = v2LabelStart + v2LabelLength + v2ChecksumAlgorithmLength = 32 + v2SaltStart = v2ChecksumAlgorithmStart + v2ChecksumAlgorithmLength + v2SaltLength = 64 + v2UUIDStart = v2SaltStart + v2SaltLength + v2UUIDLength = 40 + v2SubsystemStart = v2UUIDStart + v2UUIDLength + v2SubsystemLength = v2LabelLength + v2HeaderOffsetStart = v2SubsystemStart + v2SubsystemLength + v2HeaderOffsetLength = 8 + v2Padding1Start = v2HeaderOffsetStart + v2HeaderOffsetLength + v2Padding1Length = 184 + v2ChecksumStart = v2Padding1Start + v2Padding1Length + v2ChecksumLength = 64 + v2Padding4096Start = v2ChecksumStart + v2ChecksumLength + v2Padding4096Length = 7 * 512 + v2HeaderStructSize = v2Padding4096Start + v2Padding4096Length + + V2Stripes = 4000 + V2AlignKeyslots = 4096 + V2SectorSize = 4096 +) + +func (h V2Header) Magic() string { + return string(h[v2MagicStart : v2MagicStart+v2MagicLength]) +} + +func (h *V2Header) SetMagic(magic string) error { + switch magic { + case V2Magic1, V2Magic2: + copy(h[v2MagicStart:v2MagicStart+v2MagicLength], []uint8(magic)) + return nil + } + return fmt.Errorf("magic %q not acceptable, only %q and %q are acceptable magic values: %w", magic, V2Magic1, V2Magic2, syscall.EINVAL) +} + +func (h V2Header) readu2(offset int) uint16 { + t := uint16(0) + for i := 0; i < 2; i++ { + t = (t << 8) + uint16(h[offset+i]) + } + return t +} + +func (h V2Header) readu8(offset int) uint64 { + t := uint64(0) + for i := 0; i < 8; i++ { + t = (t << 8) + uint64(h[offset+i]) + } + return t +} + +func (h *V2Header) writeu2(offset int, value uint16) { + t := value + for i := 0; i < 2; i++ { + h[offset+1-i] = uint8(uint64(t) & 0xff) + t >>= 8 + } +} + +func (h *V2Header) writeu8(offset int, value uint64) { + t := value + for i := 0; i < 8; i++ { + h[offset+7-i] = uint8(uint64(t) & 0xff) + t >>= 8 + } +} + +func (h V2Header) Version() uint16 { + return h.readu2(v2VersionStart) +} + +func (h *V2Header) SetVersion(version uint16) error { + switch version { + case 2: + h.writeu2(v2VersionStart, version) + return nil + } + return fmt.Errorf("version %d not acceptable, only 2 is an acceptable version: %w", version, syscall.EINVAL) +} + +func (h V2Header) HeaderSize() uint64 { + return h.readu8(v2HeaderSizeStart) +} + +func (h *V2Header) SetHeaderSize(size uint64) { + h.writeu8(v2HeaderSizeStart, size) +} + +func (h V2Header) SequenceID() uint64 { + return h.readu8(v2SequenceIDStart) +} + +func (h *V2Header) SetSequenceID(id uint64) { + h.writeu8(v2SequenceIDStart, id) +} + +func trimZeroPad(s string) string { + return strings.TrimRightFunc(s, func(r rune) bool { return r == 0 }) +} + +func (h V2Header) Label() string { + return trimZeroPad(string(h[v2LabelStart : v2LabelStart+v2LabelLength])) +} + +func (h *V2Header) setZeroString(offset int, value string, length int) { + for len(value) < length { + value = value + "\000" + } + copy(h[offset:offset+length], []uint8(value)) +} + +func (h *V2Header) SetLabel(label string) { + h.setZeroString(v2LabelStart, label, v2LabelLength) +} + +func (h V2Header) ChecksumAlgorithm() string { + return trimZeroPad(string(h[v2ChecksumAlgorithmStart : v2ChecksumAlgorithmStart+v2ChecksumAlgorithmLength])) +} + +func (h *V2Header) SetChecksumAlgorithm(alg string) { + h.setZeroString(v2ChecksumAlgorithmStart, alg, v2ChecksumAlgorithmLength) +} + +func dupInt8(s []uint8) []uint8 { + c := make([]uint8, len(s)) + copy(c, s) + return c +} + +func (h *V2Header) setInt8(offset int, s []uint8, length int) { + t := make([]byte, length) + copy(t, s) + copy(h[offset:offset+length], t) +} + +func (h V2Header) Salt() []uint8 { + return dupInt8(h[v2SaltStart : v2SaltStart+v2SaltLength]) +} + +func (h *V2Header) SetSalt(salt []uint8) { + h.setInt8(v2SaltStart, salt, v2SaltLength) +} + +func (h V2Header) UUID() string { + return trimZeroPad(string(h[v2UUIDStart : v2UUIDStart+v2UUIDLength])) +} + +func (h *V2Header) SetUUID(uuid string) { + h.setZeroString(v2UUIDStart, uuid, v2UUIDLength) +} + +func (h V2Header) Subsystem() string { + return trimZeroPad(string(h[v2SubsystemStart : v2SubsystemStart+v2SubsystemLength])) +} + +func (h *V2Header) SetSubsystem(ss string) { + h.setZeroString(v2SubsystemStart, ss, v2SubsystemLength) +} + +func (h V2Header) HeaderOffset() uint64 { + return h.readu8(v2HeaderOffsetStart) +} + +func (h *V2Header) SetHeaderOffset(o uint64) { + h.writeu8(v2HeaderOffsetStart, o) +} + +func (h V2Header) Checksum() []uint8 { + hasher, err := hasherByName(h.ChecksumAlgorithm()) + if err == nil { + return dupInt8(h[v2ChecksumStart : v2ChecksumStart+hasher().Size()]) + } + return dupInt8(h[v2ChecksumStart : v2ChecksumStart+v2ChecksumLength]) +} + +func (h *V2Header) SetChecksum(sum []uint8) { + h.setInt8(v2ChecksumStart, sum, v2ChecksumLength) +} diff --git a/vendor/github.com/containers/luksy/v2json.go b/vendor/github.com/containers/luksy/v2json.go new file mode 100644 index 00000000000..5d7650d3f51 --- /dev/null +++ b/vendor/github.com/containers/luksy/v2json.go @@ -0,0 +1,157 @@ +package luksy + +type V2JSON struct { + Config V2JSONConfig `json:"config"` + Keyslots map[string]V2JSONKeyslot `json:"keyslots"` + Digests map[string]V2JSONDigest `json:"digests"` + Segments map[string]V2JSONSegment `json:"segments"` + Tokens map[string]V2JSONToken `json:"tokens"` +} + +type V2JSONKeyslotPriority int + +func (p V2JSONKeyslotPriority) String() string { + switch p { + case V2JSONKeyslotPriorityIgnore: + return "ignore" + case V2JSONKeyslotPriorityNormal: + return "normal" + case V2JSONKeyslotPriorityHigh: + return "high" + } + return "unknown" +} + +const ( + V2JSONKeyslotPriorityIgnore = V2JSONKeyslotPriority(0) + V2JSONKeyslotPriorityNormal = V2JSONKeyslotPriority(1) + V2JSONKeyslotPriorityHigh = V2JSONKeyslotPriority(2) +) + +type V2JSONKeyslot struct { + Type string `json:"type"` + KeySize int `json:"key_size"` + Area V2JSONArea `json:"area"` + Priority *V2JSONKeyslotPriority `json:"priority,omitempty"` + *V2JSONKeyslotLUKS2 // type = "luks2" + *V2JSONKeyslotReencrypt // type = "reencrypt" +} + +type V2JSONKeyslotLUKS2 struct { + AF V2JSONAF `json:"af"` + Kdf V2JSONKdf `json:"kdf"` +} + +type V2JSONKeyslotReencrypt struct { + Mode string `json:"mode"` // only "reencrypt", "encrypt", "decrypt" + Direction string `json:"direction"` // only "forward", "backward" +} + +type V2JSONArea struct { + Type string `json:"type"` // only "raw", "none", "journal", "checksum", "datashift", "datashift-journal", "datashift-checksum" + Offset int64 `json:"offset,string"` + Size int64 `json:"size,string"` + *V2JSONAreaRaw // type = "raw" + *V2JSONAreaChecksum // type = "checksum" + *V2JSONAreaDatashift // type = "datashift" + *V2JSONAreaDatashiftChecksum // type = "datashift-checksum" +} + +type V2JSONAreaRaw struct { + Encryption string `json:"encryption"` + KeySize int `json:"key_size"` +} + +type V2JSONAreaChecksum struct { + Hash string `json:"hash"` + SectorSize int `json:"sector_size"` +} + +type V2JSONAreaDatashift struct { + ShiftSize int `json:"shift_size,string"` +} + +type V2JSONAreaDatashiftChecksum struct { + V2JSONAreaChecksum + V2JSONAreaDatashift +} + +type V2JSONAF struct { + Type string `json:"type"` // "luks1" + *V2JSONAFLUKS1 // type == "luks1" +} + +type V2JSONAFLUKS1 struct { + Stripes int `json:"stripes"` // 4000 + Hash string `json:"hash"` // "sha256" +} + +type V2JSONKdf struct { + Type string `json:"type"` + Salt []byte `json:"salt"` + *V2JSONKdfPbkdf2 // type = "pbkdf2" + *V2JSONKdfArgon2i // type = "argon2i" or type = "argon2id" +} + +type V2JSONKdfPbkdf2 struct { + Hash string `json:"hash"` + Iterations int `json:"iterations"` +} + +type V2JSONKdfArgon2i struct { + Time int `json:"time"` + Memory int `json:"memory"` + CPUs int `json:"cpus"` +} + +type V2JSONSegment struct { + Type string `json:"type"` // only "linear", "crypt" + Offset string `json:"offset"` + Size string `json:"size"` // numeric value or "dynamic" + Flags []string `json:"flags,omitempty"` + *V2JSONSegmentCrypt `json:",omitempty"` // type = "crypt" +} + +type V2JSONSegmentCrypt struct { + IVTweak int `json:"iv_tweak,string"` + Encryption string `json:"encryption"` + SectorSize int `json:"sector_size"` // 512 or 1024 or 2048 or 4096 + Integrity *V2JSONSegmentIntegrity `json:"integrity,omitempty"` +} + +type V2JSONSegmentIntegrity struct { + Type string `json:"type"` + JournalEncryption string `json:"journal_encryption"` + JournalIntegrity string `json:"journal_integrity"` +} + +type V2JSONDigest struct { + Type string `json:"type"` + Keyslots []string `json:"keyslots"` + Segments []string `json:"segments"` + Salt []byte `json:"salt"` + Digest []byte `json:"digest"` + *V2JSONDigestPbkdf2 // type == "pbkdf2" +} + +type V2JSONDigestPbkdf2 struct { + Hash string `json:"hash"` + Iterations int `json:"iterations"` +} + +type V2JSONConfig struct { + JsonSize int `json:"json_size,string"` + KeyslotsSize int `json:"keyslots_size,string,omitempty"` + Flags []string `json:"flags,omitempty"` // one or more of "allow-discards", "same-cpu-crypt", "submit-from-crypt-cpus", "no-journal", "no-read-workqueue", "no-write-workqueue" + Requirements []string `json:"requirements,omitempty"` +} + +type V2JSONToken struct { + Type string `json:"type"` // "luks2-keyring" + Keyslots []string `json:"keyslots,omitempty"` + *V2JSONTokenLUKS2Keyring // type == "luks2-keyring" +} + +type V2JSONTokenLUKS2Keyring struct { + KeyDescription string `json:"key_description"` +} diff --git a/vendor/golang.org/x/crypto/argon2/argon2.go b/vendor/golang.org/x/crypto/argon2/argon2.go new file mode 100644 index 00000000000..29f0a2de451 --- /dev/null +++ b/vendor/golang.org/x/crypto/argon2/argon2.go @@ -0,0 +1,283 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package argon2 implements the key derivation function Argon2. +// Argon2 was selected as the winner of the Password Hashing Competition and can +// be used to derive cryptographic keys from passwords. +// +// For a detailed specification of Argon2 see [1]. +// +// If you aren't sure which function you need, use Argon2id (IDKey) and +// the parameter recommendations for your scenario. +// +// # Argon2i +// +// Argon2i (implemented by Key) is the side-channel resistant version of Argon2. +// It uses data-independent memory access, which is preferred for password +// hashing and password-based key derivation. Argon2i requires more passes over +// memory than Argon2id to protect from trade-off attacks. The recommended +// parameters (taken from [2]) for non-interactive operations are time=3 and to +// use the maximum available memory. +// +// # Argon2id +// +// Argon2id (implemented by IDKey) is a hybrid version of Argon2 combining +// Argon2i and Argon2d. It uses data-independent memory access for the first +// half of the first iteration over the memory and data-dependent memory access +// for the rest. Argon2id is side-channel resistant and provides better brute- +// force cost savings due to time-memory tradeoffs than Argon2i. The recommended +// parameters for non-interactive operations (taken from [2]) are time=1 and to +// use the maximum available memory. +// +// [1] https://github.com/P-H-C/phc-winner-argon2/blob/master/argon2-specs.pdf +// [2] https://tools.ietf.org/html/draft-irtf-cfrg-argon2-03#section-9.3 +package argon2 + +import ( + "encoding/binary" + "sync" + + "golang.org/x/crypto/blake2b" +) + +// The Argon2 version implemented by this package. +const Version = 0x13 + +const ( + argon2d = iota + argon2i + argon2id +) + +// Key derives a key from the password, salt, and cost parameters using Argon2i +// returning a byte slice of length keyLen that can be used as cryptographic +// key. The CPU cost and parallelism degree must be greater than zero. +// +// For example, you can get a derived key for e.g. AES-256 (which needs a +// 32-byte key) by doing: +// +// key := argon2.Key([]byte("some password"), salt, 3, 32*1024, 4, 32) +// +// The draft RFC recommends[2] time=3, and memory=32*1024 is a sensible number. +// If using that amount of memory (32 MB) is not possible in some contexts then +// the time parameter can be increased to compensate. +// +// The time parameter specifies the number of passes over the memory and the +// memory parameter specifies the size of the memory in KiB. For example +// memory=32*1024 sets the memory cost to ~32 MB. The number of threads can be +// adjusted to the number of available CPUs. The cost parameters should be +// increased as memory latency and CPU parallelism increases. Remember to get a +// good random salt. +func Key(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte { + return deriveKey(argon2i, password, salt, nil, nil, time, memory, threads, keyLen) +} + +// IDKey derives a key from the password, salt, and cost parameters using +// Argon2id returning a byte slice of length keyLen that can be used as +// cryptographic key. The CPU cost and parallelism degree must be greater than +// zero. +// +// For example, you can get a derived key for e.g. AES-256 (which needs a +// 32-byte key) by doing: +// +// key := argon2.IDKey([]byte("some password"), salt, 1, 64*1024, 4, 32) +// +// The draft RFC recommends[2] time=1, and memory=64*1024 is a sensible number. +// If using that amount of memory (64 MB) is not possible in some contexts then +// the time parameter can be increased to compensate. +// +// The time parameter specifies the number of passes over the memory and the +// memory parameter specifies the size of the memory in KiB. For example +// memory=64*1024 sets the memory cost to ~64 MB. The number of threads can be +// adjusted to the numbers of available CPUs. The cost parameters should be +// increased as memory latency and CPU parallelism increases. Remember to get a +// good random salt. +func IDKey(password, salt []byte, time, memory uint32, threads uint8, keyLen uint32) []byte { + return deriveKey(argon2id, password, salt, nil, nil, time, memory, threads, keyLen) +} + +func deriveKey(mode int, password, salt, secret, data []byte, time, memory uint32, threads uint8, keyLen uint32) []byte { + if time < 1 { + panic("argon2: number of rounds too small") + } + if threads < 1 { + panic("argon2: parallelism degree too low") + } + h0 := initHash(password, salt, secret, data, time, memory, uint32(threads), keyLen, mode) + + memory = memory / (syncPoints * uint32(threads)) * (syncPoints * uint32(threads)) + if memory < 2*syncPoints*uint32(threads) { + memory = 2 * syncPoints * uint32(threads) + } + B := initBlocks(&h0, memory, uint32(threads)) + processBlocks(B, time, memory, uint32(threads), mode) + return extractKey(B, memory, uint32(threads), keyLen) +} + +const ( + blockLength = 128 + syncPoints = 4 +) + +type block [blockLength]uint64 + +func initHash(password, salt, key, data []byte, time, memory, threads, keyLen uint32, mode int) [blake2b.Size + 8]byte { + var ( + h0 [blake2b.Size + 8]byte + params [24]byte + tmp [4]byte + ) + + b2, _ := blake2b.New512(nil) + binary.LittleEndian.PutUint32(params[0:4], threads) + binary.LittleEndian.PutUint32(params[4:8], keyLen) + binary.LittleEndian.PutUint32(params[8:12], memory) + binary.LittleEndian.PutUint32(params[12:16], time) + binary.LittleEndian.PutUint32(params[16:20], uint32(Version)) + binary.LittleEndian.PutUint32(params[20:24], uint32(mode)) + b2.Write(params[:]) + binary.LittleEndian.PutUint32(tmp[:], uint32(len(password))) + b2.Write(tmp[:]) + b2.Write(password) + binary.LittleEndian.PutUint32(tmp[:], uint32(len(salt))) + b2.Write(tmp[:]) + b2.Write(salt) + binary.LittleEndian.PutUint32(tmp[:], uint32(len(key))) + b2.Write(tmp[:]) + b2.Write(key) + binary.LittleEndian.PutUint32(tmp[:], uint32(len(data))) + b2.Write(tmp[:]) + b2.Write(data) + b2.Sum(h0[:0]) + return h0 +} + +func initBlocks(h0 *[blake2b.Size + 8]byte, memory, threads uint32) []block { + var block0 [1024]byte + B := make([]block, memory) + for lane := uint32(0); lane < threads; lane++ { + j := lane * (memory / threads) + binary.LittleEndian.PutUint32(h0[blake2b.Size+4:], lane) + + binary.LittleEndian.PutUint32(h0[blake2b.Size:], 0) + blake2bHash(block0[:], h0[:]) + for i := range B[j+0] { + B[j+0][i] = binary.LittleEndian.Uint64(block0[i*8:]) + } + + binary.LittleEndian.PutUint32(h0[blake2b.Size:], 1) + blake2bHash(block0[:], h0[:]) + for i := range B[j+1] { + B[j+1][i] = binary.LittleEndian.Uint64(block0[i*8:]) + } + } + return B +} + +func processBlocks(B []block, time, memory, threads uint32, mode int) { + lanes := memory / threads + segments := lanes / syncPoints + + processSegment := func(n, slice, lane uint32, wg *sync.WaitGroup) { + var addresses, in, zero block + if mode == argon2i || (mode == argon2id && n == 0 && slice < syncPoints/2) { + in[0] = uint64(n) + in[1] = uint64(lane) + in[2] = uint64(slice) + in[3] = uint64(memory) + in[4] = uint64(time) + in[5] = uint64(mode) + } + + index := uint32(0) + if n == 0 && slice == 0 { + index = 2 // we have already generated the first two blocks + if mode == argon2i || mode == argon2id { + in[6]++ + processBlock(&addresses, &in, &zero) + processBlock(&addresses, &addresses, &zero) + } + } + + offset := lane*lanes + slice*segments + index + var random uint64 + for index < segments { + prev := offset - 1 + if index == 0 && slice == 0 { + prev += lanes // last block in lane + } + if mode == argon2i || (mode == argon2id && n == 0 && slice < syncPoints/2) { + if index%blockLength == 0 { + in[6]++ + processBlock(&addresses, &in, &zero) + processBlock(&addresses, &addresses, &zero) + } + random = addresses[index%blockLength] + } else { + random = B[prev][0] + } + newOffset := indexAlpha(random, lanes, segments, threads, n, slice, lane, index) + processBlockXOR(&B[offset], &B[prev], &B[newOffset]) + index, offset = index+1, offset+1 + } + wg.Done() + } + + for n := uint32(0); n < time; n++ { + for slice := uint32(0); slice < syncPoints; slice++ { + var wg sync.WaitGroup + for lane := uint32(0); lane < threads; lane++ { + wg.Add(1) + go processSegment(n, slice, lane, &wg) + } + wg.Wait() + } + } + +} + +func extractKey(B []block, memory, threads, keyLen uint32) []byte { + lanes := memory / threads + for lane := uint32(0); lane < threads-1; lane++ { + for i, v := range B[(lane*lanes)+lanes-1] { + B[memory-1][i] ^= v + } + } + + var block [1024]byte + for i, v := range B[memory-1] { + binary.LittleEndian.PutUint64(block[i*8:], v) + } + key := make([]byte, keyLen) + blake2bHash(key, block[:]) + return key +} + +func indexAlpha(rand uint64, lanes, segments, threads, n, slice, lane, index uint32) uint32 { + refLane := uint32(rand>>32) % threads + if n == 0 && slice == 0 { + refLane = lane + } + m, s := 3*segments, ((slice+1)%syncPoints)*segments + if lane == refLane { + m += index + } + if n == 0 { + m, s = slice*segments, 0 + if slice == 0 || lane == refLane { + m += index + } + } + if index == 0 || lane == refLane { + m-- + } + return phi(rand, uint64(m), uint64(s), refLane, lanes) +} + +func phi(rand, m, s uint64, lane, lanes uint32) uint32 { + p := rand & 0xFFFFFFFF + p = (p * p) >> 32 + p = (p * m) >> 32 + return lane*lanes + uint32((s+m-(p+1))%uint64(lanes)) +} diff --git a/vendor/golang.org/x/crypto/argon2/blake2b.go b/vendor/golang.org/x/crypto/argon2/blake2b.go new file mode 100644 index 00000000000..10f46948dc1 --- /dev/null +++ b/vendor/golang.org/x/crypto/argon2/blake2b.go @@ -0,0 +1,53 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package argon2 + +import ( + "encoding/binary" + "hash" + + "golang.org/x/crypto/blake2b" +) + +// blake2bHash computes an arbitrary long hash value of in +// and writes the hash to out. +func blake2bHash(out []byte, in []byte) { + var b2 hash.Hash + if n := len(out); n < blake2b.Size { + b2, _ = blake2b.New(n, nil) + } else { + b2, _ = blake2b.New512(nil) + } + + var buffer [blake2b.Size]byte + binary.LittleEndian.PutUint32(buffer[:4], uint32(len(out))) + b2.Write(buffer[:4]) + b2.Write(in) + + if len(out) <= blake2b.Size { + b2.Sum(out[:0]) + return + } + + outLen := len(out) + b2.Sum(buffer[:0]) + b2.Reset() + copy(out, buffer[:32]) + out = out[32:] + for len(out) > blake2b.Size { + b2.Write(buffer[:]) + b2.Sum(buffer[:0]) + copy(out, buffer[:32]) + out = out[32:] + b2.Reset() + } + + if outLen%blake2b.Size > 0 { // outLen > 64 + r := ((outLen + 31) / 32) - 2 // ⌈τ /32⌉-2 + b2, _ = blake2b.New(outLen-32*r, nil) + } + b2.Write(buffer[:]) + b2.Sum(out[:0]) +} diff --git a/vendor/golang.org/x/crypto/argon2/blamka_amd64.go b/vendor/golang.org/x/crypto/argon2/blamka_amd64.go new file mode 100644 index 00000000000..a014ac92aa9 --- /dev/null +++ b/vendor/golang.org/x/crypto/argon2/blamka_amd64.go @@ -0,0 +1,61 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build amd64 && gc && !purego +// +build amd64,gc,!purego + +package argon2 + +import "golang.org/x/sys/cpu" + +func init() { + useSSE4 = cpu.X86.HasSSE41 +} + +//go:noescape +func mixBlocksSSE2(out, a, b, c *block) + +//go:noescape +func xorBlocksSSE2(out, a, b, c *block) + +//go:noescape +func blamkaSSE4(b *block) + +func processBlockSSE(out, in1, in2 *block, xor bool) { + var t block + mixBlocksSSE2(&t, in1, in2, &t) + if useSSE4 { + blamkaSSE4(&t) + } else { + for i := 0; i < blockLength; i += 16 { + blamkaGeneric( + &t[i+0], &t[i+1], &t[i+2], &t[i+3], + &t[i+4], &t[i+5], &t[i+6], &t[i+7], + &t[i+8], &t[i+9], &t[i+10], &t[i+11], + &t[i+12], &t[i+13], &t[i+14], &t[i+15], + ) + } + for i := 0; i < blockLength/8; i += 2 { + blamkaGeneric( + &t[i], &t[i+1], &t[16+i], &t[16+i+1], + &t[32+i], &t[32+i+1], &t[48+i], &t[48+i+1], + &t[64+i], &t[64+i+1], &t[80+i], &t[80+i+1], + &t[96+i], &t[96+i+1], &t[112+i], &t[112+i+1], + ) + } + } + if xor { + xorBlocksSSE2(out, in1, in2, &t) + } else { + mixBlocksSSE2(out, in1, in2, &t) + } +} + +func processBlock(out, in1, in2 *block) { + processBlockSSE(out, in1, in2, false) +} + +func processBlockXOR(out, in1, in2 *block) { + processBlockSSE(out, in1, in2, true) +} diff --git a/vendor/golang.org/x/crypto/argon2/blamka_amd64.s b/vendor/golang.org/x/crypto/argon2/blamka_amd64.s new file mode 100644 index 00000000000..b2cc0515049 --- /dev/null +++ b/vendor/golang.org/x/crypto/argon2/blamka_amd64.s @@ -0,0 +1,244 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build amd64 && gc && !purego +// +build amd64,gc,!purego + +#include "textflag.h" + +DATA ·c40<>+0x00(SB)/8, $0x0201000706050403 +DATA ·c40<>+0x08(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·c40<>(SB), (NOPTR+RODATA), $16 + +DATA ·c48<>+0x00(SB)/8, $0x0100070605040302 +DATA ·c48<>+0x08(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·c48<>(SB), (NOPTR+RODATA), $16 + +#define SHUFFLE(v2, v3, v4, v5, v6, v7, t1, t2) \ + MOVO v4, t1; \ + MOVO v5, v4; \ + MOVO t1, v5; \ + MOVO v6, t1; \ + PUNPCKLQDQ v6, t2; \ + PUNPCKHQDQ v7, v6; \ + PUNPCKHQDQ t2, v6; \ + PUNPCKLQDQ v7, t2; \ + MOVO t1, v7; \ + MOVO v2, t1; \ + PUNPCKHQDQ t2, v7; \ + PUNPCKLQDQ v3, t2; \ + PUNPCKHQDQ t2, v2; \ + PUNPCKLQDQ t1, t2; \ + PUNPCKHQDQ t2, v3 + +#define SHUFFLE_INV(v2, v3, v4, v5, v6, v7, t1, t2) \ + MOVO v4, t1; \ + MOVO v5, v4; \ + MOVO t1, v5; \ + MOVO v2, t1; \ + PUNPCKLQDQ v2, t2; \ + PUNPCKHQDQ v3, v2; \ + PUNPCKHQDQ t2, v2; \ + PUNPCKLQDQ v3, t2; \ + MOVO t1, v3; \ + MOVO v6, t1; \ + PUNPCKHQDQ t2, v3; \ + PUNPCKLQDQ v7, t2; \ + PUNPCKHQDQ t2, v6; \ + PUNPCKLQDQ t1, t2; \ + PUNPCKHQDQ t2, v7 + +#define HALF_ROUND(v0, v1, v2, v3, v4, v5, v6, v7, t0, c40, c48) \ + MOVO v0, t0; \ + PMULULQ v2, t0; \ + PADDQ v2, v0; \ + PADDQ t0, v0; \ + PADDQ t0, v0; \ + PXOR v0, v6; \ + PSHUFD $0xB1, v6, v6; \ + MOVO v4, t0; \ + PMULULQ v6, t0; \ + PADDQ v6, v4; \ + PADDQ t0, v4; \ + PADDQ t0, v4; \ + PXOR v4, v2; \ + PSHUFB c40, v2; \ + MOVO v0, t0; \ + PMULULQ v2, t0; \ + PADDQ v2, v0; \ + PADDQ t0, v0; \ + PADDQ t0, v0; \ + PXOR v0, v6; \ + PSHUFB c48, v6; \ + MOVO v4, t0; \ + PMULULQ v6, t0; \ + PADDQ v6, v4; \ + PADDQ t0, v4; \ + PADDQ t0, v4; \ + PXOR v4, v2; \ + MOVO v2, t0; \ + PADDQ v2, t0; \ + PSRLQ $63, v2; \ + PXOR t0, v2; \ + MOVO v1, t0; \ + PMULULQ v3, t0; \ + PADDQ v3, v1; \ + PADDQ t0, v1; \ + PADDQ t0, v1; \ + PXOR v1, v7; \ + PSHUFD $0xB1, v7, v7; \ + MOVO v5, t0; \ + PMULULQ v7, t0; \ + PADDQ v7, v5; \ + PADDQ t0, v5; \ + PADDQ t0, v5; \ + PXOR v5, v3; \ + PSHUFB c40, v3; \ + MOVO v1, t0; \ + PMULULQ v3, t0; \ + PADDQ v3, v1; \ + PADDQ t0, v1; \ + PADDQ t0, v1; \ + PXOR v1, v7; \ + PSHUFB c48, v7; \ + MOVO v5, t0; \ + PMULULQ v7, t0; \ + PADDQ v7, v5; \ + PADDQ t0, v5; \ + PADDQ t0, v5; \ + PXOR v5, v3; \ + MOVO v3, t0; \ + PADDQ v3, t0; \ + PSRLQ $63, v3; \ + PXOR t0, v3 + +#define LOAD_MSG_0(block, off) \ + MOVOU 8*(off+0)(block), X0; \ + MOVOU 8*(off+2)(block), X1; \ + MOVOU 8*(off+4)(block), X2; \ + MOVOU 8*(off+6)(block), X3; \ + MOVOU 8*(off+8)(block), X4; \ + MOVOU 8*(off+10)(block), X5; \ + MOVOU 8*(off+12)(block), X6; \ + MOVOU 8*(off+14)(block), X7 + +#define STORE_MSG_0(block, off) \ + MOVOU X0, 8*(off+0)(block); \ + MOVOU X1, 8*(off+2)(block); \ + MOVOU X2, 8*(off+4)(block); \ + MOVOU X3, 8*(off+6)(block); \ + MOVOU X4, 8*(off+8)(block); \ + MOVOU X5, 8*(off+10)(block); \ + MOVOU X6, 8*(off+12)(block); \ + MOVOU X7, 8*(off+14)(block) + +#define LOAD_MSG_1(block, off) \ + MOVOU 8*off+0*8(block), X0; \ + MOVOU 8*off+16*8(block), X1; \ + MOVOU 8*off+32*8(block), X2; \ + MOVOU 8*off+48*8(block), X3; \ + MOVOU 8*off+64*8(block), X4; \ + MOVOU 8*off+80*8(block), X5; \ + MOVOU 8*off+96*8(block), X6; \ + MOVOU 8*off+112*8(block), X7 + +#define STORE_MSG_1(block, off) \ + MOVOU X0, 8*off+0*8(block); \ + MOVOU X1, 8*off+16*8(block); \ + MOVOU X2, 8*off+32*8(block); \ + MOVOU X3, 8*off+48*8(block); \ + MOVOU X4, 8*off+64*8(block); \ + MOVOU X5, 8*off+80*8(block); \ + MOVOU X6, 8*off+96*8(block); \ + MOVOU X7, 8*off+112*8(block) + +#define BLAMKA_ROUND_0(block, off, t0, t1, c40, c48) \ + LOAD_MSG_0(block, off); \ + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48); \ + SHUFFLE(X2, X3, X4, X5, X6, X7, t0, t1); \ + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48); \ + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, t0, t1); \ + STORE_MSG_0(block, off) + +#define BLAMKA_ROUND_1(block, off, t0, t1, c40, c48) \ + LOAD_MSG_1(block, off); \ + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48); \ + SHUFFLE(X2, X3, X4, X5, X6, X7, t0, t1); \ + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48); \ + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, t0, t1); \ + STORE_MSG_1(block, off) + +// func blamkaSSE4(b *block) +TEXT ·blamkaSSE4(SB), 4, $0-8 + MOVQ b+0(FP), AX + + MOVOU ·c40<>(SB), X10 + MOVOU ·c48<>(SB), X11 + + BLAMKA_ROUND_0(AX, 0, X8, X9, X10, X11) + BLAMKA_ROUND_0(AX, 16, X8, X9, X10, X11) + BLAMKA_ROUND_0(AX, 32, X8, X9, X10, X11) + BLAMKA_ROUND_0(AX, 48, X8, X9, X10, X11) + BLAMKA_ROUND_0(AX, 64, X8, X9, X10, X11) + BLAMKA_ROUND_0(AX, 80, X8, X9, X10, X11) + BLAMKA_ROUND_0(AX, 96, X8, X9, X10, X11) + BLAMKA_ROUND_0(AX, 112, X8, X9, X10, X11) + + BLAMKA_ROUND_1(AX, 0, X8, X9, X10, X11) + BLAMKA_ROUND_1(AX, 2, X8, X9, X10, X11) + BLAMKA_ROUND_1(AX, 4, X8, X9, X10, X11) + BLAMKA_ROUND_1(AX, 6, X8, X9, X10, X11) + BLAMKA_ROUND_1(AX, 8, X8, X9, X10, X11) + BLAMKA_ROUND_1(AX, 10, X8, X9, X10, X11) + BLAMKA_ROUND_1(AX, 12, X8, X9, X10, X11) + BLAMKA_ROUND_1(AX, 14, X8, X9, X10, X11) + RET + +// func mixBlocksSSE2(out, a, b, c *block) +TEXT ·mixBlocksSSE2(SB), 4, $0-32 + MOVQ out+0(FP), DX + MOVQ a+8(FP), AX + MOVQ b+16(FP), BX + MOVQ a+24(FP), CX + MOVQ $128, BP + +loop: + MOVOU 0(AX), X0 + MOVOU 0(BX), X1 + MOVOU 0(CX), X2 + PXOR X1, X0 + PXOR X2, X0 + MOVOU X0, 0(DX) + ADDQ $16, AX + ADDQ $16, BX + ADDQ $16, CX + ADDQ $16, DX + SUBQ $2, BP + JA loop + RET + +// func xorBlocksSSE2(out, a, b, c *block) +TEXT ·xorBlocksSSE2(SB), 4, $0-32 + MOVQ out+0(FP), DX + MOVQ a+8(FP), AX + MOVQ b+16(FP), BX + MOVQ a+24(FP), CX + MOVQ $128, BP + +loop: + MOVOU 0(AX), X0 + MOVOU 0(BX), X1 + MOVOU 0(CX), X2 + MOVOU 0(DX), X3 + PXOR X1, X0 + PXOR X2, X0 + PXOR X3, X0 + MOVOU X0, 0(DX) + ADDQ $16, AX + ADDQ $16, BX + ADDQ $16, CX + ADDQ $16, DX + SUBQ $2, BP + JA loop + RET diff --git a/vendor/golang.org/x/crypto/argon2/blamka_generic.go b/vendor/golang.org/x/crypto/argon2/blamka_generic.go new file mode 100644 index 00000000000..a481b2243f8 --- /dev/null +++ b/vendor/golang.org/x/crypto/argon2/blamka_generic.go @@ -0,0 +1,163 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package argon2 + +var useSSE4 bool + +func processBlockGeneric(out, in1, in2 *block, xor bool) { + var t block + for i := range t { + t[i] = in1[i] ^ in2[i] + } + for i := 0; i < blockLength; i += 16 { + blamkaGeneric( + &t[i+0], &t[i+1], &t[i+2], &t[i+3], + &t[i+4], &t[i+5], &t[i+6], &t[i+7], + &t[i+8], &t[i+9], &t[i+10], &t[i+11], + &t[i+12], &t[i+13], &t[i+14], &t[i+15], + ) + } + for i := 0; i < blockLength/8; i += 2 { + blamkaGeneric( + &t[i], &t[i+1], &t[16+i], &t[16+i+1], + &t[32+i], &t[32+i+1], &t[48+i], &t[48+i+1], + &t[64+i], &t[64+i+1], &t[80+i], &t[80+i+1], + &t[96+i], &t[96+i+1], &t[112+i], &t[112+i+1], + ) + } + if xor { + for i := range t { + out[i] ^= in1[i] ^ in2[i] ^ t[i] + } + } else { + for i := range t { + out[i] = in1[i] ^ in2[i] ^ t[i] + } + } +} + +func blamkaGeneric(t00, t01, t02, t03, t04, t05, t06, t07, t08, t09, t10, t11, t12, t13, t14, t15 *uint64) { + v00, v01, v02, v03 := *t00, *t01, *t02, *t03 + v04, v05, v06, v07 := *t04, *t05, *t06, *t07 + v08, v09, v10, v11 := *t08, *t09, *t10, *t11 + v12, v13, v14, v15 := *t12, *t13, *t14, *t15 + + v00 += v04 + 2*uint64(uint32(v00))*uint64(uint32(v04)) + v12 ^= v00 + v12 = v12>>32 | v12<<32 + v08 += v12 + 2*uint64(uint32(v08))*uint64(uint32(v12)) + v04 ^= v08 + v04 = v04>>24 | v04<<40 + + v00 += v04 + 2*uint64(uint32(v00))*uint64(uint32(v04)) + v12 ^= v00 + v12 = v12>>16 | v12<<48 + v08 += v12 + 2*uint64(uint32(v08))*uint64(uint32(v12)) + v04 ^= v08 + v04 = v04>>63 | v04<<1 + + v01 += v05 + 2*uint64(uint32(v01))*uint64(uint32(v05)) + v13 ^= v01 + v13 = v13>>32 | v13<<32 + v09 += v13 + 2*uint64(uint32(v09))*uint64(uint32(v13)) + v05 ^= v09 + v05 = v05>>24 | v05<<40 + + v01 += v05 + 2*uint64(uint32(v01))*uint64(uint32(v05)) + v13 ^= v01 + v13 = v13>>16 | v13<<48 + v09 += v13 + 2*uint64(uint32(v09))*uint64(uint32(v13)) + v05 ^= v09 + v05 = v05>>63 | v05<<1 + + v02 += v06 + 2*uint64(uint32(v02))*uint64(uint32(v06)) + v14 ^= v02 + v14 = v14>>32 | v14<<32 + v10 += v14 + 2*uint64(uint32(v10))*uint64(uint32(v14)) + v06 ^= v10 + v06 = v06>>24 | v06<<40 + + v02 += v06 + 2*uint64(uint32(v02))*uint64(uint32(v06)) + v14 ^= v02 + v14 = v14>>16 | v14<<48 + v10 += v14 + 2*uint64(uint32(v10))*uint64(uint32(v14)) + v06 ^= v10 + v06 = v06>>63 | v06<<1 + + v03 += v07 + 2*uint64(uint32(v03))*uint64(uint32(v07)) + v15 ^= v03 + v15 = v15>>32 | v15<<32 + v11 += v15 + 2*uint64(uint32(v11))*uint64(uint32(v15)) + v07 ^= v11 + v07 = v07>>24 | v07<<40 + + v03 += v07 + 2*uint64(uint32(v03))*uint64(uint32(v07)) + v15 ^= v03 + v15 = v15>>16 | v15<<48 + v11 += v15 + 2*uint64(uint32(v11))*uint64(uint32(v15)) + v07 ^= v11 + v07 = v07>>63 | v07<<1 + + v00 += v05 + 2*uint64(uint32(v00))*uint64(uint32(v05)) + v15 ^= v00 + v15 = v15>>32 | v15<<32 + v10 += v15 + 2*uint64(uint32(v10))*uint64(uint32(v15)) + v05 ^= v10 + v05 = v05>>24 | v05<<40 + + v00 += v05 + 2*uint64(uint32(v00))*uint64(uint32(v05)) + v15 ^= v00 + v15 = v15>>16 | v15<<48 + v10 += v15 + 2*uint64(uint32(v10))*uint64(uint32(v15)) + v05 ^= v10 + v05 = v05>>63 | v05<<1 + + v01 += v06 + 2*uint64(uint32(v01))*uint64(uint32(v06)) + v12 ^= v01 + v12 = v12>>32 | v12<<32 + v11 += v12 + 2*uint64(uint32(v11))*uint64(uint32(v12)) + v06 ^= v11 + v06 = v06>>24 | v06<<40 + + v01 += v06 + 2*uint64(uint32(v01))*uint64(uint32(v06)) + v12 ^= v01 + v12 = v12>>16 | v12<<48 + v11 += v12 + 2*uint64(uint32(v11))*uint64(uint32(v12)) + v06 ^= v11 + v06 = v06>>63 | v06<<1 + + v02 += v07 + 2*uint64(uint32(v02))*uint64(uint32(v07)) + v13 ^= v02 + v13 = v13>>32 | v13<<32 + v08 += v13 + 2*uint64(uint32(v08))*uint64(uint32(v13)) + v07 ^= v08 + v07 = v07>>24 | v07<<40 + + v02 += v07 + 2*uint64(uint32(v02))*uint64(uint32(v07)) + v13 ^= v02 + v13 = v13>>16 | v13<<48 + v08 += v13 + 2*uint64(uint32(v08))*uint64(uint32(v13)) + v07 ^= v08 + v07 = v07>>63 | v07<<1 + + v03 += v04 + 2*uint64(uint32(v03))*uint64(uint32(v04)) + v14 ^= v03 + v14 = v14>>32 | v14<<32 + v09 += v14 + 2*uint64(uint32(v09))*uint64(uint32(v14)) + v04 ^= v09 + v04 = v04>>24 | v04<<40 + + v03 += v04 + 2*uint64(uint32(v03))*uint64(uint32(v04)) + v14 ^= v03 + v14 = v14>>16 | v14<<48 + v09 += v14 + 2*uint64(uint32(v09))*uint64(uint32(v14)) + v04 ^= v09 + v04 = v04>>63 | v04<<1 + + *t00, *t01, *t02, *t03 = v00, v01, v02, v03 + *t04, *t05, *t06, *t07 = v04, v05, v06, v07 + *t08, *t09, *t10, *t11 = v08, v09, v10, v11 + *t12, *t13, *t14, *t15 = v12, v13, v14, v15 +} diff --git a/vendor/golang.org/x/crypto/argon2/blamka_ref.go b/vendor/golang.org/x/crypto/argon2/blamka_ref.go new file mode 100644 index 00000000000..167c59d2d5a --- /dev/null +++ b/vendor/golang.org/x/crypto/argon2/blamka_ref.go @@ -0,0 +1,16 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !amd64 || purego || !gc +// +build !amd64 purego !gc + +package argon2 + +func processBlock(out, in1, in2 *block) { + processBlockGeneric(out, in1, in2, false) +} + +func processBlockXOR(out, in1, in2 *block) { + processBlockGeneric(out, in1, in2, true) +} diff --git a/vendor/golang.org/x/crypto/blake2b/blake2b.go b/vendor/golang.org/x/crypto/blake2b/blake2b.go new file mode 100644 index 00000000000..d2e98d4295b --- /dev/null +++ b/vendor/golang.org/x/crypto/blake2b/blake2b.go @@ -0,0 +1,291 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package blake2b implements the BLAKE2b hash algorithm defined by RFC 7693 +// and the extendable output function (XOF) BLAKE2Xb. +// +// BLAKE2b is optimized for 64-bit platforms—including NEON-enabled ARMs—and +// produces digests of any size between 1 and 64 bytes. +// For a detailed specification of BLAKE2b see https://blake2.net/blake2.pdf +// and for BLAKE2Xb see https://blake2.net/blake2x.pdf +// +// If you aren't sure which function you need, use BLAKE2b (Sum512 or New512). +// If you need a secret-key MAC (message authentication code), use the New512 +// function with a non-nil key. +// +// BLAKE2X is a construction to compute hash values larger than 64 bytes. It +// can produce hash values between 0 and 4 GiB. +package blake2b + +import ( + "encoding/binary" + "errors" + "hash" +) + +const ( + // The blocksize of BLAKE2b in bytes. + BlockSize = 128 + // The hash size of BLAKE2b-512 in bytes. + Size = 64 + // The hash size of BLAKE2b-384 in bytes. + Size384 = 48 + // The hash size of BLAKE2b-256 in bytes. + Size256 = 32 +) + +var ( + useAVX2 bool + useAVX bool + useSSE4 bool +) + +var ( + errKeySize = errors.New("blake2b: invalid key size") + errHashSize = errors.New("blake2b: invalid hash size") +) + +var iv = [8]uint64{ + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, +} + +// Sum512 returns the BLAKE2b-512 checksum of the data. +func Sum512(data []byte) [Size]byte { + var sum [Size]byte + checkSum(&sum, Size, data) + return sum +} + +// Sum384 returns the BLAKE2b-384 checksum of the data. +func Sum384(data []byte) [Size384]byte { + var sum [Size]byte + var sum384 [Size384]byte + checkSum(&sum, Size384, data) + copy(sum384[:], sum[:Size384]) + return sum384 +} + +// Sum256 returns the BLAKE2b-256 checksum of the data. +func Sum256(data []byte) [Size256]byte { + var sum [Size]byte + var sum256 [Size256]byte + checkSum(&sum, Size256, data) + copy(sum256[:], sum[:Size256]) + return sum256 +} + +// New512 returns a new hash.Hash computing the BLAKE2b-512 checksum. A non-nil +// key turns the hash into a MAC. The key must be between zero and 64 bytes long. +func New512(key []byte) (hash.Hash, error) { return newDigest(Size, key) } + +// New384 returns a new hash.Hash computing the BLAKE2b-384 checksum. A non-nil +// key turns the hash into a MAC. The key must be between zero and 64 bytes long. +func New384(key []byte) (hash.Hash, error) { return newDigest(Size384, key) } + +// New256 returns a new hash.Hash computing the BLAKE2b-256 checksum. A non-nil +// key turns the hash into a MAC. The key must be between zero and 64 bytes long. +func New256(key []byte) (hash.Hash, error) { return newDigest(Size256, key) } + +// New returns a new hash.Hash computing the BLAKE2b checksum with a custom length. +// A non-nil key turns the hash into a MAC. The key must be between zero and 64 bytes long. +// The hash size can be a value between 1 and 64 but it is highly recommended to use +// values equal or greater than: +// - 32 if BLAKE2b is used as a hash function (The key is zero bytes long). +// - 16 if BLAKE2b is used as a MAC function (The key is at least 16 bytes long). +// When the key is nil, the returned hash.Hash implements BinaryMarshaler +// and BinaryUnmarshaler for state (de)serialization as documented by hash.Hash. +func New(size int, key []byte) (hash.Hash, error) { return newDigest(size, key) } + +func newDigest(hashSize int, key []byte) (*digest, error) { + if hashSize < 1 || hashSize > Size { + return nil, errHashSize + } + if len(key) > Size { + return nil, errKeySize + } + d := &digest{ + size: hashSize, + keyLen: len(key), + } + copy(d.key[:], key) + d.Reset() + return d, nil +} + +func checkSum(sum *[Size]byte, hashSize int, data []byte) { + h := iv + h[0] ^= uint64(hashSize) | (1 << 16) | (1 << 24) + var c [2]uint64 + + if length := len(data); length > BlockSize { + n := length &^ (BlockSize - 1) + if length == n { + n -= BlockSize + } + hashBlocks(&h, &c, 0, data[:n]) + data = data[n:] + } + + var block [BlockSize]byte + offset := copy(block[:], data) + remaining := uint64(BlockSize - offset) + if c[0] < remaining { + c[1]-- + } + c[0] -= remaining + + hashBlocks(&h, &c, 0xFFFFFFFFFFFFFFFF, block[:]) + + for i, v := range h[:(hashSize+7)/8] { + binary.LittleEndian.PutUint64(sum[8*i:], v) + } +} + +type digest struct { + h [8]uint64 + c [2]uint64 + size int + block [BlockSize]byte + offset int + + key [BlockSize]byte + keyLen int +} + +const ( + magic = "b2b" + marshaledSize = len(magic) + 8*8 + 2*8 + 1 + BlockSize + 1 +) + +func (d *digest) MarshalBinary() ([]byte, error) { + if d.keyLen != 0 { + return nil, errors.New("crypto/blake2b: cannot marshal MACs") + } + b := make([]byte, 0, marshaledSize) + b = append(b, magic...) + for i := 0; i < 8; i++ { + b = appendUint64(b, d.h[i]) + } + b = appendUint64(b, d.c[0]) + b = appendUint64(b, d.c[1]) + // Maximum value for size is 64 + b = append(b, byte(d.size)) + b = append(b, d.block[:]...) + b = append(b, byte(d.offset)) + return b, nil +} + +func (d *digest) UnmarshalBinary(b []byte) error { + if len(b) < len(magic) || string(b[:len(magic)]) != magic { + return errors.New("crypto/blake2b: invalid hash state identifier") + } + if len(b) != marshaledSize { + return errors.New("crypto/blake2b: invalid hash state size") + } + b = b[len(magic):] + for i := 0; i < 8; i++ { + b, d.h[i] = consumeUint64(b) + } + b, d.c[0] = consumeUint64(b) + b, d.c[1] = consumeUint64(b) + d.size = int(b[0]) + b = b[1:] + copy(d.block[:], b[:BlockSize]) + b = b[BlockSize:] + d.offset = int(b[0]) + return nil +} + +func (d *digest) BlockSize() int { return BlockSize } + +func (d *digest) Size() int { return d.size } + +func (d *digest) Reset() { + d.h = iv + d.h[0] ^= uint64(d.size) | (uint64(d.keyLen) << 8) | (1 << 16) | (1 << 24) + d.offset, d.c[0], d.c[1] = 0, 0, 0 + if d.keyLen > 0 { + d.block = d.key + d.offset = BlockSize + } +} + +func (d *digest) Write(p []byte) (n int, err error) { + n = len(p) + + if d.offset > 0 { + remaining := BlockSize - d.offset + if n <= remaining { + d.offset += copy(d.block[d.offset:], p) + return + } + copy(d.block[d.offset:], p[:remaining]) + hashBlocks(&d.h, &d.c, 0, d.block[:]) + d.offset = 0 + p = p[remaining:] + } + + if length := len(p); length > BlockSize { + nn := length &^ (BlockSize - 1) + if length == nn { + nn -= BlockSize + } + hashBlocks(&d.h, &d.c, 0, p[:nn]) + p = p[nn:] + } + + if len(p) > 0 { + d.offset += copy(d.block[:], p) + } + + return +} + +func (d *digest) Sum(sum []byte) []byte { + var hash [Size]byte + d.finalize(&hash) + return append(sum, hash[:d.size]...) +} + +func (d *digest) finalize(hash *[Size]byte) { + var block [BlockSize]byte + copy(block[:], d.block[:d.offset]) + remaining := uint64(BlockSize - d.offset) + + c := d.c + if c[0] < remaining { + c[1]-- + } + c[0] -= remaining + + h := d.h + hashBlocks(&h, &c, 0xFFFFFFFFFFFFFFFF, block[:]) + + for i, v := range h { + binary.LittleEndian.PutUint64(hash[8*i:], v) + } +} + +func appendUint64(b []byte, x uint64) []byte { + var a [8]byte + binary.BigEndian.PutUint64(a[:], x) + return append(b, a[:]...) +} + +func appendUint32(b []byte, x uint32) []byte { + var a [4]byte + binary.BigEndian.PutUint32(a[:], x) + return append(b, a[:]...) +} + +func consumeUint64(b []byte) ([]byte, uint64) { + x := binary.BigEndian.Uint64(b) + return b[8:], x +} + +func consumeUint32(b []byte) ([]byte, uint32) { + x := binary.BigEndian.Uint32(b) + return b[4:], x +} diff --git a/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go b/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go new file mode 100644 index 00000000000..56bfaaa17da --- /dev/null +++ b/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.go @@ -0,0 +1,38 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.7 && amd64 && gc && !purego +// +build go1.7,amd64,gc,!purego + +package blake2b + +import "golang.org/x/sys/cpu" + +func init() { + useAVX2 = cpu.X86.HasAVX2 + useAVX = cpu.X86.HasAVX + useSSE4 = cpu.X86.HasSSE41 +} + +//go:noescape +func hashBlocksAVX2(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) + +//go:noescape +func hashBlocksAVX(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) + +//go:noescape +func hashBlocksSSE4(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) + +func hashBlocks(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) { + switch { + case useAVX2: + hashBlocksAVX2(h, c, flag, blocks) + case useAVX: + hashBlocksAVX(h, c, flag, blocks) + case useSSE4: + hashBlocksSSE4(h, c, flag, blocks) + default: + hashBlocksGeneric(h, c, flag, blocks) + } +} diff --git a/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s b/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s new file mode 100644 index 00000000000..4b9daa18d9d --- /dev/null +++ b/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s @@ -0,0 +1,745 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.7 && amd64 && gc && !purego +// +build go1.7,amd64,gc,!purego + +#include "textflag.h" + +DATA ·AVX2_iv0<>+0x00(SB)/8, $0x6a09e667f3bcc908 +DATA ·AVX2_iv0<>+0x08(SB)/8, $0xbb67ae8584caa73b +DATA ·AVX2_iv0<>+0x10(SB)/8, $0x3c6ef372fe94f82b +DATA ·AVX2_iv0<>+0x18(SB)/8, $0xa54ff53a5f1d36f1 +GLOBL ·AVX2_iv0<>(SB), (NOPTR+RODATA), $32 + +DATA ·AVX2_iv1<>+0x00(SB)/8, $0x510e527fade682d1 +DATA ·AVX2_iv1<>+0x08(SB)/8, $0x9b05688c2b3e6c1f +DATA ·AVX2_iv1<>+0x10(SB)/8, $0x1f83d9abfb41bd6b +DATA ·AVX2_iv1<>+0x18(SB)/8, $0x5be0cd19137e2179 +GLOBL ·AVX2_iv1<>(SB), (NOPTR+RODATA), $32 + +DATA ·AVX2_c40<>+0x00(SB)/8, $0x0201000706050403 +DATA ·AVX2_c40<>+0x08(SB)/8, $0x0a09080f0e0d0c0b +DATA ·AVX2_c40<>+0x10(SB)/8, $0x0201000706050403 +DATA ·AVX2_c40<>+0x18(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·AVX2_c40<>(SB), (NOPTR+RODATA), $32 + +DATA ·AVX2_c48<>+0x00(SB)/8, $0x0100070605040302 +DATA ·AVX2_c48<>+0x08(SB)/8, $0x09080f0e0d0c0b0a +DATA ·AVX2_c48<>+0x10(SB)/8, $0x0100070605040302 +DATA ·AVX2_c48<>+0x18(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·AVX2_c48<>(SB), (NOPTR+RODATA), $32 + +DATA ·AVX_iv0<>+0x00(SB)/8, $0x6a09e667f3bcc908 +DATA ·AVX_iv0<>+0x08(SB)/8, $0xbb67ae8584caa73b +GLOBL ·AVX_iv0<>(SB), (NOPTR+RODATA), $16 + +DATA ·AVX_iv1<>+0x00(SB)/8, $0x3c6ef372fe94f82b +DATA ·AVX_iv1<>+0x08(SB)/8, $0xa54ff53a5f1d36f1 +GLOBL ·AVX_iv1<>(SB), (NOPTR+RODATA), $16 + +DATA ·AVX_iv2<>+0x00(SB)/8, $0x510e527fade682d1 +DATA ·AVX_iv2<>+0x08(SB)/8, $0x9b05688c2b3e6c1f +GLOBL ·AVX_iv2<>(SB), (NOPTR+RODATA), $16 + +DATA ·AVX_iv3<>+0x00(SB)/8, $0x1f83d9abfb41bd6b +DATA ·AVX_iv3<>+0x08(SB)/8, $0x5be0cd19137e2179 +GLOBL ·AVX_iv3<>(SB), (NOPTR+RODATA), $16 + +DATA ·AVX_c40<>+0x00(SB)/8, $0x0201000706050403 +DATA ·AVX_c40<>+0x08(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·AVX_c40<>(SB), (NOPTR+RODATA), $16 + +DATA ·AVX_c48<>+0x00(SB)/8, $0x0100070605040302 +DATA ·AVX_c48<>+0x08(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·AVX_c48<>(SB), (NOPTR+RODATA), $16 + +#define VPERMQ_0x39_Y1_Y1 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xc9; BYTE $0x39 +#define VPERMQ_0x93_Y1_Y1 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xc9; BYTE $0x93 +#define VPERMQ_0x4E_Y2_Y2 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xd2; BYTE $0x4e +#define VPERMQ_0x93_Y3_Y3 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xdb; BYTE $0x93 +#define VPERMQ_0x39_Y3_Y3 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xdb; BYTE $0x39 + +#define ROUND_AVX2(m0, m1, m2, m3, t, c40, c48) \ + VPADDQ m0, Y0, Y0; \ + VPADDQ Y1, Y0, Y0; \ + VPXOR Y0, Y3, Y3; \ + VPSHUFD $-79, Y3, Y3; \ + VPADDQ Y3, Y2, Y2; \ + VPXOR Y2, Y1, Y1; \ + VPSHUFB c40, Y1, Y1; \ + VPADDQ m1, Y0, Y0; \ + VPADDQ Y1, Y0, Y0; \ + VPXOR Y0, Y3, Y3; \ + VPSHUFB c48, Y3, Y3; \ + VPADDQ Y3, Y2, Y2; \ + VPXOR Y2, Y1, Y1; \ + VPADDQ Y1, Y1, t; \ + VPSRLQ $63, Y1, Y1; \ + VPXOR t, Y1, Y1; \ + VPERMQ_0x39_Y1_Y1; \ + VPERMQ_0x4E_Y2_Y2; \ + VPERMQ_0x93_Y3_Y3; \ + VPADDQ m2, Y0, Y0; \ + VPADDQ Y1, Y0, Y0; \ + VPXOR Y0, Y3, Y3; \ + VPSHUFD $-79, Y3, Y3; \ + VPADDQ Y3, Y2, Y2; \ + VPXOR Y2, Y1, Y1; \ + VPSHUFB c40, Y1, Y1; \ + VPADDQ m3, Y0, Y0; \ + VPADDQ Y1, Y0, Y0; \ + VPXOR Y0, Y3, Y3; \ + VPSHUFB c48, Y3, Y3; \ + VPADDQ Y3, Y2, Y2; \ + VPXOR Y2, Y1, Y1; \ + VPADDQ Y1, Y1, t; \ + VPSRLQ $63, Y1, Y1; \ + VPXOR t, Y1, Y1; \ + VPERMQ_0x39_Y3_Y3; \ + VPERMQ_0x4E_Y2_Y2; \ + VPERMQ_0x93_Y1_Y1 + +#define VMOVQ_SI_X11_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x1E +#define VMOVQ_SI_X12_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x26 +#define VMOVQ_SI_X13_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x2E +#define VMOVQ_SI_X14_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x36 +#define VMOVQ_SI_X15_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x3E + +#define VMOVQ_SI_X11(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x5E; BYTE $n +#define VMOVQ_SI_X12(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x66; BYTE $n +#define VMOVQ_SI_X13(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x6E; BYTE $n +#define VMOVQ_SI_X14(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x76; BYTE $n +#define VMOVQ_SI_X15(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x7E; BYTE $n + +#define VPINSRQ_1_SI_X11_0 BYTE $0xC4; BYTE $0x63; BYTE $0xA1; BYTE $0x22; BYTE $0x1E; BYTE $0x01 +#define VPINSRQ_1_SI_X12_0 BYTE $0xC4; BYTE $0x63; BYTE $0x99; BYTE $0x22; BYTE $0x26; BYTE $0x01 +#define VPINSRQ_1_SI_X13_0 BYTE $0xC4; BYTE $0x63; BYTE $0x91; BYTE $0x22; BYTE $0x2E; BYTE $0x01 +#define VPINSRQ_1_SI_X14_0 BYTE $0xC4; BYTE $0x63; BYTE $0x89; BYTE $0x22; BYTE $0x36; BYTE $0x01 +#define VPINSRQ_1_SI_X15_0 BYTE $0xC4; BYTE $0x63; BYTE $0x81; BYTE $0x22; BYTE $0x3E; BYTE $0x01 + +#define VPINSRQ_1_SI_X11(n) BYTE $0xC4; BYTE $0x63; BYTE $0xA1; BYTE $0x22; BYTE $0x5E; BYTE $n; BYTE $0x01 +#define VPINSRQ_1_SI_X12(n) BYTE $0xC4; BYTE $0x63; BYTE $0x99; BYTE $0x22; BYTE $0x66; BYTE $n; BYTE $0x01 +#define VPINSRQ_1_SI_X13(n) BYTE $0xC4; BYTE $0x63; BYTE $0x91; BYTE $0x22; BYTE $0x6E; BYTE $n; BYTE $0x01 +#define VPINSRQ_1_SI_X14(n) BYTE $0xC4; BYTE $0x63; BYTE $0x89; BYTE $0x22; BYTE $0x76; BYTE $n; BYTE $0x01 +#define VPINSRQ_1_SI_X15(n) BYTE $0xC4; BYTE $0x63; BYTE $0x81; BYTE $0x22; BYTE $0x7E; BYTE $n; BYTE $0x01 + +#define VMOVQ_R8_X15 BYTE $0xC4; BYTE $0x41; BYTE $0xF9; BYTE $0x6E; BYTE $0xF8 +#define VPINSRQ_1_R9_X15 BYTE $0xC4; BYTE $0x43; BYTE $0x81; BYTE $0x22; BYTE $0xF9; BYTE $0x01 + +// load msg: Y12 = (i0, i1, i2, i3) +// i0, i1, i2, i3 must not be 0 +#define LOAD_MSG_AVX2_Y12(i0, i1, i2, i3) \ + VMOVQ_SI_X12(i0*8); \ + VMOVQ_SI_X11(i2*8); \ + VPINSRQ_1_SI_X12(i1*8); \ + VPINSRQ_1_SI_X11(i3*8); \ + VINSERTI128 $1, X11, Y12, Y12 + +// load msg: Y13 = (i0, i1, i2, i3) +// i0, i1, i2, i3 must not be 0 +#define LOAD_MSG_AVX2_Y13(i0, i1, i2, i3) \ + VMOVQ_SI_X13(i0*8); \ + VMOVQ_SI_X11(i2*8); \ + VPINSRQ_1_SI_X13(i1*8); \ + VPINSRQ_1_SI_X11(i3*8); \ + VINSERTI128 $1, X11, Y13, Y13 + +// load msg: Y14 = (i0, i1, i2, i3) +// i0, i1, i2, i3 must not be 0 +#define LOAD_MSG_AVX2_Y14(i0, i1, i2, i3) \ + VMOVQ_SI_X14(i0*8); \ + VMOVQ_SI_X11(i2*8); \ + VPINSRQ_1_SI_X14(i1*8); \ + VPINSRQ_1_SI_X11(i3*8); \ + VINSERTI128 $1, X11, Y14, Y14 + +// load msg: Y15 = (i0, i1, i2, i3) +// i0, i1, i2, i3 must not be 0 +#define LOAD_MSG_AVX2_Y15(i0, i1, i2, i3) \ + VMOVQ_SI_X15(i0*8); \ + VMOVQ_SI_X11(i2*8); \ + VPINSRQ_1_SI_X15(i1*8); \ + VPINSRQ_1_SI_X11(i3*8); \ + VINSERTI128 $1, X11, Y15, Y15 + +#define LOAD_MSG_AVX2_0_2_4_6_1_3_5_7_8_10_12_14_9_11_13_15() \ + VMOVQ_SI_X12_0; \ + VMOVQ_SI_X11(4*8); \ + VPINSRQ_1_SI_X12(2*8); \ + VPINSRQ_1_SI_X11(6*8); \ + VINSERTI128 $1, X11, Y12, Y12; \ + LOAD_MSG_AVX2_Y13(1, 3, 5, 7); \ + LOAD_MSG_AVX2_Y14(8, 10, 12, 14); \ + LOAD_MSG_AVX2_Y15(9, 11, 13, 15) + +#define LOAD_MSG_AVX2_14_4_9_13_10_8_15_6_1_0_11_5_12_2_7_3() \ + LOAD_MSG_AVX2_Y12(14, 4, 9, 13); \ + LOAD_MSG_AVX2_Y13(10, 8, 15, 6); \ + VMOVQ_SI_X11(11*8); \ + VPSHUFD $0x4E, 0*8(SI), X14; \ + VPINSRQ_1_SI_X11(5*8); \ + VINSERTI128 $1, X11, Y14, Y14; \ + LOAD_MSG_AVX2_Y15(12, 2, 7, 3) + +#define LOAD_MSG_AVX2_11_12_5_15_8_0_2_13_10_3_7_9_14_6_1_4() \ + VMOVQ_SI_X11(5*8); \ + VMOVDQU 11*8(SI), X12; \ + VPINSRQ_1_SI_X11(15*8); \ + VINSERTI128 $1, X11, Y12, Y12; \ + VMOVQ_SI_X13(8*8); \ + VMOVQ_SI_X11(2*8); \ + VPINSRQ_1_SI_X13_0; \ + VPINSRQ_1_SI_X11(13*8); \ + VINSERTI128 $1, X11, Y13, Y13; \ + LOAD_MSG_AVX2_Y14(10, 3, 7, 9); \ + LOAD_MSG_AVX2_Y15(14, 6, 1, 4) + +#define LOAD_MSG_AVX2_7_3_13_11_9_1_12_14_2_5_4_15_6_10_0_8() \ + LOAD_MSG_AVX2_Y12(7, 3, 13, 11); \ + LOAD_MSG_AVX2_Y13(9, 1, 12, 14); \ + LOAD_MSG_AVX2_Y14(2, 5, 4, 15); \ + VMOVQ_SI_X15(6*8); \ + VMOVQ_SI_X11_0; \ + VPINSRQ_1_SI_X15(10*8); \ + VPINSRQ_1_SI_X11(8*8); \ + VINSERTI128 $1, X11, Y15, Y15 + +#define LOAD_MSG_AVX2_9_5_2_10_0_7_4_15_14_11_6_3_1_12_8_13() \ + LOAD_MSG_AVX2_Y12(9, 5, 2, 10); \ + VMOVQ_SI_X13_0; \ + VMOVQ_SI_X11(4*8); \ + VPINSRQ_1_SI_X13(7*8); \ + VPINSRQ_1_SI_X11(15*8); \ + VINSERTI128 $1, X11, Y13, Y13; \ + LOAD_MSG_AVX2_Y14(14, 11, 6, 3); \ + LOAD_MSG_AVX2_Y15(1, 12, 8, 13) + +#define LOAD_MSG_AVX2_2_6_0_8_12_10_11_3_4_7_15_1_13_5_14_9() \ + VMOVQ_SI_X12(2*8); \ + VMOVQ_SI_X11_0; \ + VPINSRQ_1_SI_X12(6*8); \ + VPINSRQ_1_SI_X11(8*8); \ + VINSERTI128 $1, X11, Y12, Y12; \ + LOAD_MSG_AVX2_Y13(12, 10, 11, 3); \ + LOAD_MSG_AVX2_Y14(4, 7, 15, 1); \ + LOAD_MSG_AVX2_Y15(13, 5, 14, 9) + +#define LOAD_MSG_AVX2_12_1_14_4_5_15_13_10_0_6_9_8_7_3_2_11() \ + LOAD_MSG_AVX2_Y12(12, 1, 14, 4); \ + LOAD_MSG_AVX2_Y13(5, 15, 13, 10); \ + VMOVQ_SI_X14_0; \ + VPSHUFD $0x4E, 8*8(SI), X11; \ + VPINSRQ_1_SI_X14(6*8); \ + VINSERTI128 $1, X11, Y14, Y14; \ + LOAD_MSG_AVX2_Y15(7, 3, 2, 11) + +#define LOAD_MSG_AVX2_13_7_12_3_11_14_1_9_5_15_8_2_0_4_6_10() \ + LOAD_MSG_AVX2_Y12(13, 7, 12, 3); \ + LOAD_MSG_AVX2_Y13(11, 14, 1, 9); \ + LOAD_MSG_AVX2_Y14(5, 15, 8, 2); \ + VMOVQ_SI_X15_0; \ + VMOVQ_SI_X11(6*8); \ + VPINSRQ_1_SI_X15(4*8); \ + VPINSRQ_1_SI_X11(10*8); \ + VINSERTI128 $1, X11, Y15, Y15 + +#define LOAD_MSG_AVX2_6_14_11_0_15_9_3_8_12_13_1_10_2_7_4_5() \ + VMOVQ_SI_X12(6*8); \ + VMOVQ_SI_X11(11*8); \ + VPINSRQ_1_SI_X12(14*8); \ + VPINSRQ_1_SI_X11_0; \ + VINSERTI128 $1, X11, Y12, Y12; \ + LOAD_MSG_AVX2_Y13(15, 9, 3, 8); \ + VMOVQ_SI_X11(1*8); \ + VMOVDQU 12*8(SI), X14; \ + VPINSRQ_1_SI_X11(10*8); \ + VINSERTI128 $1, X11, Y14, Y14; \ + VMOVQ_SI_X15(2*8); \ + VMOVDQU 4*8(SI), X11; \ + VPINSRQ_1_SI_X15(7*8); \ + VINSERTI128 $1, X11, Y15, Y15 + +#define LOAD_MSG_AVX2_10_8_7_1_2_4_6_5_15_9_3_13_11_14_12_0() \ + LOAD_MSG_AVX2_Y12(10, 8, 7, 1); \ + VMOVQ_SI_X13(2*8); \ + VPSHUFD $0x4E, 5*8(SI), X11; \ + VPINSRQ_1_SI_X13(4*8); \ + VINSERTI128 $1, X11, Y13, Y13; \ + LOAD_MSG_AVX2_Y14(15, 9, 3, 13); \ + VMOVQ_SI_X15(11*8); \ + VMOVQ_SI_X11(12*8); \ + VPINSRQ_1_SI_X15(14*8); \ + VPINSRQ_1_SI_X11_0; \ + VINSERTI128 $1, X11, Y15, Y15 + +// func hashBlocksAVX2(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) +TEXT ·hashBlocksAVX2(SB), 4, $320-48 // frame size = 288 + 32 byte alignment + MOVQ h+0(FP), AX + MOVQ c+8(FP), BX + MOVQ flag+16(FP), CX + MOVQ blocks_base+24(FP), SI + MOVQ blocks_len+32(FP), DI + + MOVQ SP, DX + ADDQ $31, DX + ANDQ $~31, DX + + MOVQ CX, 16(DX) + XORQ CX, CX + MOVQ CX, 24(DX) + + VMOVDQU ·AVX2_c40<>(SB), Y4 + VMOVDQU ·AVX2_c48<>(SB), Y5 + + VMOVDQU 0(AX), Y8 + VMOVDQU 32(AX), Y9 + VMOVDQU ·AVX2_iv0<>(SB), Y6 + VMOVDQU ·AVX2_iv1<>(SB), Y7 + + MOVQ 0(BX), R8 + MOVQ 8(BX), R9 + MOVQ R9, 8(DX) + +loop: + ADDQ $128, R8 + MOVQ R8, 0(DX) + CMPQ R8, $128 + JGE noinc + INCQ R9 + MOVQ R9, 8(DX) + +noinc: + VMOVDQA Y8, Y0 + VMOVDQA Y9, Y1 + VMOVDQA Y6, Y2 + VPXOR 0(DX), Y7, Y3 + + LOAD_MSG_AVX2_0_2_4_6_1_3_5_7_8_10_12_14_9_11_13_15() + VMOVDQA Y12, 32(DX) + VMOVDQA Y13, 64(DX) + VMOVDQA Y14, 96(DX) + VMOVDQA Y15, 128(DX) + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + LOAD_MSG_AVX2_14_4_9_13_10_8_15_6_1_0_11_5_12_2_7_3() + VMOVDQA Y12, 160(DX) + VMOVDQA Y13, 192(DX) + VMOVDQA Y14, 224(DX) + VMOVDQA Y15, 256(DX) + + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + LOAD_MSG_AVX2_11_12_5_15_8_0_2_13_10_3_7_9_14_6_1_4() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + LOAD_MSG_AVX2_7_3_13_11_9_1_12_14_2_5_4_15_6_10_0_8() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + LOAD_MSG_AVX2_9_5_2_10_0_7_4_15_14_11_6_3_1_12_8_13() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + LOAD_MSG_AVX2_2_6_0_8_12_10_11_3_4_7_15_1_13_5_14_9() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + LOAD_MSG_AVX2_12_1_14_4_5_15_13_10_0_6_9_8_7_3_2_11() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + LOAD_MSG_AVX2_13_7_12_3_11_14_1_9_5_15_8_2_0_4_6_10() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + LOAD_MSG_AVX2_6_14_11_0_15_9_3_8_12_13_1_10_2_7_4_5() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + LOAD_MSG_AVX2_10_8_7_1_2_4_6_5_15_9_3_13_11_14_12_0() + ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) + + ROUND_AVX2(32(DX), 64(DX), 96(DX), 128(DX), Y10, Y4, Y5) + ROUND_AVX2(160(DX), 192(DX), 224(DX), 256(DX), Y10, Y4, Y5) + + VPXOR Y0, Y8, Y8 + VPXOR Y1, Y9, Y9 + VPXOR Y2, Y8, Y8 + VPXOR Y3, Y9, Y9 + + LEAQ 128(SI), SI + SUBQ $128, DI + JNE loop + + MOVQ R8, 0(BX) + MOVQ R9, 8(BX) + + VMOVDQU Y8, 0(AX) + VMOVDQU Y9, 32(AX) + VZEROUPPER + + RET + +#define VPUNPCKLQDQ_X2_X2_X15 BYTE $0xC5; BYTE $0x69; BYTE $0x6C; BYTE $0xFA +#define VPUNPCKLQDQ_X3_X3_X15 BYTE $0xC5; BYTE $0x61; BYTE $0x6C; BYTE $0xFB +#define VPUNPCKLQDQ_X7_X7_X15 BYTE $0xC5; BYTE $0x41; BYTE $0x6C; BYTE $0xFF +#define VPUNPCKLQDQ_X13_X13_X15 BYTE $0xC4; BYTE $0x41; BYTE $0x11; BYTE $0x6C; BYTE $0xFD +#define VPUNPCKLQDQ_X14_X14_X15 BYTE $0xC4; BYTE $0x41; BYTE $0x09; BYTE $0x6C; BYTE $0xFE + +#define VPUNPCKHQDQ_X15_X2_X2 BYTE $0xC4; BYTE $0xC1; BYTE $0x69; BYTE $0x6D; BYTE $0xD7 +#define VPUNPCKHQDQ_X15_X3_X3 BYTE $0xC4; BYTE $0xC1; BYTE $0x61; BYTE $0x6D; BYTE $0xDF +#define VPUNPCKHQDQ_X15_X6_X6 BYTE $0xC4; BYTE $0xC1; BYTE $0x49; BYTE $0x6D; BYTE $0xF7 +#define VPUNPCKHQDQ_X15_X7_X7 BYTE $0xC4; BYTE $0xC1; BYTE $0x41; BYTE $0x6D; BYTE $0xFF +#define VPUNPCKHQDQ_X15_X3_X2 BYTE $0xC4; BYTE $0xC1; BYTE $0x61; BYTE $0x6D; BYTE $0xD7 +#define VPUNPCKHQDQ_X15_X7_X6 BYTE $0xC4; BYTE $0xC1; BYTE $0x41; BYTE $0x6D; BYTE $0xF7 +#define VPUNPCKHQDQ_X15_X13_X3 BYTE $0xC4; BYTE $0xC1; BYTE $0x11; BYTE $0x6D; BYTE $0xDF +#define VPUNPCKHQDQ_X15_X13_X7 BYTE $0xC4; BYTE $0xC1; BYTE $0x11; BYTE $0x6D; BYTE $0xFF + +#define SHUFFLE_AVX() \ + VMOVDQA X6, X13; \ + VMOVDQA X2, X14; \ + VMOVDQA X4, X6; \ + VPUNPCKLQDQ_X13_X13_X15; \ + VMOVDQA X5, X4; \ + VMOVDQA X6, X5; \ + VPUNPCKHQDQ_X15_X7_X6; \ + VPUNPCKLQDQ_X7_X7_X15; \ + VPUNPCKHQDQ_X15_X13_X7; \ + VPUNPCKLQDQ_X3_X3_X15; \ + VPUNPCKHQDQ_X15_X2_X2; \ + VPUNPCKLQDQ_X14_X14_X15; \ + VPUNPCKHQDQ_X15_X3_X3; \ + +#define SHUFFLE_AVX_INV() \ + VMOVDQA X2, X13; \ + VMOVDQA X4, X14; \ + VPUNPCKLQDQ_X2_X2_X15; \ + VMOVDQA X5, X4; \ + VPUNPCKHQDQ_X15_X3_X2; \ + VMOVDQA X14, X5; \ + VPUNPCKLQDQ_X3_X3_X15; \ + VMOVDQA X6, X14; \ + VPUNPCKHQDQ_X15_X13_X3; \ + VPUNPCKLQDQ_X7_X7_X15; \ + VPUNPCKHQDQ_X15_X6_X6; \ + VPUNPCKLQDQ_X14_X14_X15; \ + VPUNPCKHQDQ_X15_X7_X7; \ + +#define HALF_ROUND_AVX(v0, v1, v2, v3, v4, v5, v6, v7, m0, m1, m2, m3, t0, c40, c48) \ + VPADDQ m0, v0, v0; \ + VPADDQ v2, v0, v0; \ + VPADDQ m1, v1, v1; \ + VPADDQ v3, v1, v1; \ + VPXOR v0, v6, v6; \ + VPXOR v1, v7, v7; \ + VPSHUFD $-79, v6, v6; \ + VPSHUFD $-79, v7, v7; \ + VPADDQ v6, v4, v4; \ + VPADDQ v7, v5, v5; \ + VPXOR v4, v2, v2; \ + VPXOR v5, v3, v3; \ + VPSHUFB c40, v2, v2; \ + VPSHUFB c40, v3, v3; \ + VPADDQ m2, v0, v0; \ + VPADDQ v2, v0, v0; \ + VPADDQ m3, v1, v1; \ + VPADDQ v3, v1, v1; \ + VPXOR v0, v6, v6; \ + VPXOR v1, v7, v7; \ + VPSHUFB c48, v6, v6; \ + VPSHUFB c48, v7, v7; \ + VPADDQ v6, v4, v4; \ + VPADDQ v7, v5, v5; \ + VPXOR v4, v2, v2; \ + VPXOR v5, v3, v3; \ + VPADDQ v2, v2, t0; \ + VPSRLQ $63, v2, v2; \ + VPXOR t0, v2, v2; \ + VPADDQ v3, v3, t0; \ + VPSRLQ $63, v3, v3; \ + VPXOR t0, v3, v3 + +// load msg: X12 = (i0, i1), X13 = (i2, i3), X14 = (i4, i5), X15 = (i6, i7) +// i0, i1, i2, i3, i4, i5, i6, i7 must not be 0 +#define LOAD_MSG_AVX(i0, i1, i2, i3, i4, i5, i6, i7) \ + VMOVQ_SI_X12(i0*8); \ + VMOVQ_SI_X13(i2*8); \ + VMOVQ_SI_X14(i4*8); \ + VMOVQ_SI_X15(i6*8); \ + VPINSRQ_1_SI_X12(i1*8); \ + VPINSRQ_1_SI_X13(i3*8); \ + VPINSRQ_1_SI_X14(i5*8); \ + VPINSRQ_1_SI_X15(i7*8) + +// load msg: X12 = (0, 2), X13 = (4, 6), X14 = (1, 3), X15 = (5, 7) +#define LOAD_MSG_AVX_0_2_4_6_1_3_5_7() \ + VMOVQ_SI_X12_0; \ + VMOVQ_SI_X13(4*8); \ + VMOVQ_SI_X14(1*8); \ + VMOVQ_SI_X15(5*8); \ + VPINSRQ_1_SI_X12(2*8); \ + VPINSRQ_1_SI_X13(6*8); \ + VPINSRQ_1_SI_X14(3*8); \ + VPINSRQ_1_SI_X15(7*8) + +// load msg: X12 = (1, 0), X13 = (11, 5), X14 = (12, 2), X15 = (7, 3) +#define LOAD_MSG_AVX_1_0_11_5_12_2_7_3() \ + VPSHUFD $0x4E, 0*8(SI), X12; \ + VMOVQ_SI_X13(11*8); \ + VMOVQ_SI_X14(12*8); \ + VMOVQ_SI_X15(7*8); \ + VPINSRQ_1_SI_X13(5*8); \ + VPINSRQ_1_SI_X14(2*8); \ + VPINSRQ_1_SI_X15(3*8) + +// load msg: X12 = (11, 12), X13 = (5, 15), X14 = (8, 0), X15 = (2, 13) +#define LOAD_MSG_AVX_11_12_5_15_8_0_2_13() \ + VMOVDQU 11*8(SI), X12; \ + VMOVQ_SI_X13(5*8); \ + VMOVQ_SI_X14(8*8); \ + VMOVQ_SI_X15(2*8); \ + VPINSRQ_1_SI_X13(15*8); \ + VPINSRQ_1_SI_X14_0; \ + VPINSRQ_1_SI_X15(13*8) + +// load msg: X12 = (2, 5), X13 = (4, 15), X14 = (6, 10), X15 = (0, 8) +#define LOAD_MSG_AVX_2_5_4_15_6_10_0_8() \ + VMOVQ_SI_X12(2*8); \ + VMOVQ_SI_X13(4*8); \ + VMOVQ_SI_X14(6*8); \ + VMOVQ_SI_X15_0; \ + VPINSRQ_1_SI_X12(5*8); \ + VPINSRQ_1_SI_X13(15*8); \ + VPINSRQ_1_SI_X14(10*8); \ + VPINSRQ_1_SI_X15(8*8) + +// load msg: X12 = (9, 5), X13 = (2, 10), X14 = (0, 7), X15 = (4, 15) +#define LOAD_MSG_AVX_9_5_2_10_0_7_4_15() \ + VMOVQ_SI_X12(9*8); \ + VMOVQ_SI_X13(2*8); \ + VMOVQ_SI_X14_0; \ + VMOVQ_SI_X15(4*8); \ + VPINSRQ_1_SI_X12(5*8); \ + VPINSRQ_1_SI_X13(10*8); \ + VPINSRQ_1_SI_X14(7*8); \ + VPINSRQ_1_SI_X15(15*8) + +// load msg: X12 = (2, 6), X13 = (0, 8), X14 = (12, 10), X15 = (11, 3) +#define LOAD_MSG_AVX_2_6_0_8_12_10_11_3() \ + VMOVQ_SI_X12(2*8); \ + VMOVQ_SI_X13_0; \ + VMOVQ_SI_X14(12*8); \ + VMOVQ_SI_X15(11*8); \ + VPINSRQ_1_SI_X12(6*8); \ + VPINSRQ_1_SI_X13(8*8); \ + VPINSRQ_1_SI_X14(10*8); \ + VPINSRQ_1_SI_X15(3*8) + +// load msg: X12 = (0, 6), X13 = (9, 8), X14 = (7, 3), X15 = (2, 11) +#define LOAD_MSG_AVX_0_6_9_8_7_3_2_11() \ + MOVQ 0*8(SI), X12; \ + VPSHUFD $0x4E, 8*8(SI), X13; \ + MOVQ 7*8(SI), X14; \ + MOVQ 2*8(SI), X15; \ + VPINSRQ_1_SI_X12(6*8); \ + VPINSRQ_1_SI_X14(3*8); \ + VPINSRQ_1_SI_X15(11*8) + +// load msg: X12 = (6, 14), X13 = (11, 0), X14 = (15, 9), X15 = (3, 8) +#define LOAD_MSG_AVX_6_14_11_0_15_9_3_8() \ + MOVQ 6*8(SI), X12; \ + MOVQ 11*8(SI), X13; \ + MOVQ 15*8(SI), X14; \ + MOVQ 3*8(SI), X15; \ + VPINSRQ_1_SI_X12(14*8); \ + VPINSRQ_1_SI_X13_0; \ + VPINSRQ_1_SI_X14(9*8); \ + VPINSRQ_1_SI_X15(8*8) + +// load msg: X12 = (5, 15), X13 = (8, 2), X14 = (0, 4), X15 = (6, 10) +#define LOAD_MSG_AVX_5_15_8_2_0_4_6_10() \ + MOVQ 5*8(SI), X12; \ + MOVQ 8*8(SI), X13; \ + MOVQ 0*8(SI), X14; \ + MOVQ 6*8(SI), X15; \ + VPINSRQ_1_SI_X12(15*8); \ + VPINSRQ_1_SI_X13(2*8); \ + VPINSRQ_1_SI_X14(4*8); \ + VPINSRQ_1_SI_X15(10*8) + +// load msg: X12 = (12, 13), X13 = (1, 10), X14 = (2, 7), X15 = (4, 5) +#define LOAD_MSG_AVX_12_13_1_10_2_7_4_5() \ + VMOVDQU 12*8(SI), X12; \ + MOVQ 1*8(SI), X13; \ + MOVQ 2*8(SI), X14; \ + VPINSRQ_1_SI_X13(10*8); \ + VPINSRQ_1_SI_X14(7*8); \ + VMOVDQU 4*8(SI), X15 + +// load msg: X12 = (15, 9), X13 = (3, 13), X14 = (11, 14), X15 = (12, 0) +#define LOAD_MSG_AVX_15_9_3_13_11_14_12_0() \ + MOVQ 15*8(SI), X12; \ + MOVQ 3*8(SI), X13; \ + MOVQ 11*8(SI), X14; \ + MOVQ 12*8(SI), X15; \ + VPINSRQ_1_SI_X12(9*8); \ + VPINSRQ_1_SI_X13(13*8); \ + VPINSRQ_1_SI_X14(14*8); \ + VPINSRQ_1_SI_X15_0 + +// func hashBlocksAVX(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) +TEXT ·hashBlocksAVX(SB), 4, $288-48 // frame size = 272 + 16 byte alignment + MOVQ h+0(FP), AX + MOVQ c+8(FP), BX + MOVQ flag+16(FP), CX + MOVQ blocks_base+24(FP), SI + MOVQ blocks_len+32(FP), DI + + MOVQ SP, R10 + ADDQ $15, R10 + ANDQ $~15, R10 + + VMOVDQU ·AVX_c40<>(SB), X0 + VMOVDQU ·AVX_c48<>(SB), X1 + VMOVDQA X0, X8 + VMOVDQA X1, X9 + + VMOVDQU ·AVX_iv3<>(SB), X0 + VMOVDQA X0, 0(R10) + XORQ CX, 0(R10) // 0(R10) = ·AVX_iv3 ^ (CX || 0) + + VMOVDQU 0(AX), X10 + VMOVDQU 16(AX), X11 + VMOVDQU 32(AX), X2 + VMOVDQU 48(AX), X3 + + MOVQ 0(BX), R8 + MOVQ 8(BX), R9 + +loop: + ADDQ $128, R8 + CMPQ R8, $128 + JGE noinc + INCQ R9 + +noinc: + VMOVQ_R8_X15 + VPINSRQ_1_R9_X15 + + VMOVDQA X10, X0 + VMOVDQA X11, X1 + VMOVDQU ·AVX_iv0<>(SB), X4 + VMOVDQU ·AVX_iv1<>(SB), X5 + VMOVDQU ·AVX_iv2<>(SB), X6 + + VPXOR X15, X6, X6 + VMOVDQA 0(R10), X7 + + LOAD_MSG_AVX_0_2_4_6_1_3_5_7() + VMOVDQA X12, 16(R10) + VMOVDQA X13, 32(R10) + VMOVDQA X14, 48(R10) + VMOVDQA X15, 64(R10) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX(8, 10, 12, 14, 9, 11, 13, 15) + VMOVDQA X12, 80(R10) + VMOVDQA X13, 96(R10) + VMOVDQA X14, 112(R10) + VMOVDQA X15, 128(R10) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + LOAD_MSG_AVX(14, 4, 9, 13, 10, 8, 15, 6) + VMOVDQA X12, 144(R10) + VMOVDQA X13, 160(R10) + VMOVDQA X14, 176(R10) + VMOVDQA X15, 192(R10) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_1_0_11_5_12_2_7_3() + VMOVDQA X12, 208(R10) + VMOVDQA X13, 224(R10) + VMOVDQA X14, 240(R10) + VMOVDQA X15, 256(R10) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + LOAD_MSG_AVX_11_12_5_15_8_0_2_13() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX(10, 3, 7, 9, 14, 6, 1, 4) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + LOAD_MSG_AVX(7, 3, 13, 11, 9, 1, 12, 14) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_2_5_4_15_6_10_0_8() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + LOAD_MSG_AVX_9_5_2_10_0_7_4_15() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX(14, 11, 6, 3, 1, 12, 8, 13) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + LOAD_MSG_AVX_2_6_0_8_12_10_11_3() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX(4, 7, 15, 1, 13, 5, 14, 9) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + LOAD_MSG_AVX(12, 1, 14, 4, 5, 15, 13, 10) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_0_6_9_8_7_3_2_11() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + LOAD_MSG_AVX(13, 7, 12, 3, 11, 14, 1, 9) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_5_15_8_2_0_4_6_10() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + LOAD_MSG_AVX_6_14_11_0_15_9_3_8() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_12_13_1_10_2_7_4_5() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + LOAD_MSG_AVX(10, 8, 7, 1, 2, 4, 6, 5) + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX() + LOAD_MSG_AVX_15_9_3_13_11_14_12_0() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) + SHUFFLE_AVX_INV() + + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 16(R10), 32(R10), 48(R10), 64(R10), X15, X8, X9) + SHUFFLE_AVX() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 80(R10), 96(R10), 112(R10), 128(R10), X15, X8, X9) + SHUFFLE_AVX_INV() + + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 144(R10), 160(R10), 176(R10), 192(R10), X15, X8, X9) + SHUFFLE_AVX() + HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 208(R10), 224(R10), 240(R10), 256(R10), X15, X8, X9) + SHUFFLE_AVX_INV() + + VMOVDQU 32(AX), X14 + VMOVDQU 48(AX), X15 + VPXOR X0, X10, X10 + VPXOR X1, X11, X11 + VPXOR X2, X14, X14 + VPXOR X3, X15, X15 + VPXOR X4, X10, X10 + VPXOR X5, X11, X11 + VPXOR X6, X14, X2 + VPXOR X7, X15, X3 + VMOVDQU X2, 32(AX) + VMOVDQU X3, 48(AX) + + LEAQ 128(SI), SI + SUBQ $128, DI + JNE loop + + VMOVDQU X10, 0(AX) + VMOVDQU X11, 16(AX) + + MOVQ R8, 0(BX) + MOVQ R9, 8(BX) + VZEROUPPER + + RET diff --git a/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.go b/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.go new file mode 100644 index 00000000000..5fa1b32841d --- /dev/null +++ b/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.go @@ -0,0 +1,25 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.7 && amd64 && gc && !purego +// +build !go1.7,amd64,gc,!purego + +package blake2b + +import "golang.org/x/sys/cpu" + +func init() { + useSSE4 = cpu.X86.HasSSE41 +} + +//go:noescape +func hashBlocksSSE4(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) + +func hashBlocks(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) { + if useSSE4 { + hashBlocksSSE4(h, c, flag, blocks) + } else { + hashBlocksGeneric(h, c, flag, blocks) + } +} diff --git a/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s b/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s new file mode 100644 index 00000000000..ae75eb9afcd --- /dev/null +++ b/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s @@ -0,0 +1,279 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build amd64 && gc && !purego +// +build amd64,gc,!purego + +#include "textflag.h" + +DATA ·iv0<>+0x00(SB)/8, $0x6a09e667f3bcc908 +DATA ·iv0<>+0x08(SB)/8, $0xbb67ae8584caa73b +GLOBL ·iv0<>(SB), (NOPTR+RODATA), $16 + +DATA ·iv1<>+0x00(SB)/8, $0x3c6ef372fe94f82b +DATA ·iv1<>+0x08(SB)/8, $0xa54ff53a5f1d36f1 +GLOBL ·iv1<>(SB), (NOPTR+RODATA), $16 + +DATA ·iv2<>+0x00(SB)/8, $0x510e527fade682d1 +DATA ·iv2<>+0x08(SB)/8, $0x9b05688c2b3e6c1f +GLOBL ·iv2<>(SB), (NOPTR+RODATA), $16 + +DATA ·iv3<>+0x00(SB)/8, $0x1f83d9abfb41bd6b +DATA ·iv3<>+0x08(SB)/8, $0x5be0cd19137e2179 +GLOBL ·iv3<>(SB), (NOPTR+RODATA), $16 + +DATA ·c40<>+0x00(SB)/8, $0x0201000706050403 +DATA ·c40<>+0x08(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·c40<>(SB), (NOPTR+RODATA), $16 + +DATA ·c48<>+0x00(SB)/8, $0x0100070605040302 +DATA ·c48<>+0x08(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·c48<>(SB), (NOPTR+RODATA), $16 + +#define SHUFFLE(v2, v3, v4, v5, v6, v7, t1, t2) \ + MOVO v4, t1; \ + MOVO v5, v4; \ + MOVO t1, v5; \ + MOVO v6, t1; \ + PUNPCKLQDQ v6, t2; \ + PUNPCKHQDQ v7, v6; \ + PUNPCKHQDQ t2, v6; \ + PUNPCKLQDQ v7, t2; \ + MOVO t1, v7; \ + MOVO v2, t1; \ + PUNPCKHQDQ t2, v7; \ + PUNPCKLQDQ v3, t2; \ + PUNPCKHQDQ t2, v2; \ + PUNPCKLQDQ t1, t2; \ + PUNPCKHQDQ t2, v3 + +#define SHUFFLE_INV(v2, v3, v4, v5, v6, v7, t1, t2) \ + MOVO v4, t1; \ + MOVO v5, v4; \ + MOVO t1, v5; \ + MOVO v2, t1; \ + PUNPCKLQDQ v2, t2; \ + PUNPCKHQDQ v3, v2; \ + PUNPCKHQDQ t2, v2; \ + PUNPCKLQDQ v3, t2; \ + MOVO t1, v3; \ + MOVO v6, t1; \ + PUNPCKHQDQ t2, v3; \ + PUNPCKLQDQ v7, t2; \ + PUNPCKHQDQ t2, v6; \ + PUNPCKLQDQ t1, t2; \ + PUNPCKHQDQ t2, v7 + +#define HALF_ROUND(v0, v1, v2, v3, v4, v5, v6, v7, m0, m1, m2, m3, t0, c40, c48) \ + PADDQ m0, v0; \ + PADDQ m1, v1; \ + PADDQ v2, v0; \ + PADDQ v3, v1; \ + PXOR v0, v6; \ + PXOR v1, v7; \ + PSHUFD $0xB1, v6, v6; \ + PSHUFD $0xB1, v7, v7; \ + PADDQ v6, v4; \ + PADDQ v7, v5; \ + PXOR v4, v2; \ + PXOR v5, v3; \ + PSHUFB c40, v2; \ + PSHUFB c40, v3; \ + PADDQ m2, v0; \ + PADDQ m3, v1; \ + PADDQ v2, v0; \ + PADDQ v3, v1; \ + PXOR v0, v6; \ + PXOR v1, v7; \ + PSHUFB c48, v6; \ + PSHUFB c48, v7; \ + PADDQ v6, v4; \ + PADDQ v7, v5; \ + PXOR v4, v2; \ + PXOR v5, v3; \ + MOVOU v2, t0; \ + PADDQ v2, t0; \ + PSRLQ $63, v2; \ + PXOR t0, v2; \ + MOVOU v3, t0; \ + PADDQ v3, t0; \ + PSRLQ $63, v3; \ + PXOR t0, v3 + +#define LOAD_MSG(m0, m1, m2, m3, src, i0, i1, i2, i3, i4, i5, i6, i7) \ + MOVQ i0*8(src), m0; \ + PINSRQ $1, i1*8(src), m0; \ + MOVQ i2*8(src), m1; \ + PINSRQ $1, i3*8(src), m1; \ + MOVQ i4*8(src), m2; \ + PINSRQ $1, i5*8(src), m2; \ + MOVQ i6*8(src), m3; \ + PINSRQ $1, i7*8(src), m3 + +// func hashBlocksSSE4(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) +TEXT ·hashBlocksSSE4(SB), 4, $288-48 // frame size = 272 + 16 byte alignment + MOVQ h+0(FP), AX + MOVQ c+8(FP), BX + MOVQ flag+16(FP), CX + MOVQ blocks_base+24(FP), SI + MOVQ blocks_len+32(FP), DI + + MOVQ SP, R10 + ADDQ $15, R10 + ANDQ $~15, R10 + + MOVOU ·iv3<>(SB), X0 + MOVO X0, 0(R10) + XORQ CX, 0(R10) // 0(R10) = ·iv3 ^ (CX || 0) + + MOVOU ·c40<>(SB), X13 + MOVOU ·c48<>(SB), X14 + + MOVOU 0(AX), X12 + MOVOU 16(AX), X15 + + MOVQ 0(BX), R8 + MOVQ 8(BX), R9 + +loop: + ADDQ $128, R8 + CMPQ R8, $128 + JGE noinc + INCQ R9 + +noinc: + MOVQ R8, X8 + PINSRQ $1, R9, X8 + + MOVO X12, X0 + MOVO X15, X1 + MOVOU 32(AX), X2 + MOVOU 48(AX), X3 + MOVOU ·iv0<>(SB), X4 + MOVOU ·iv1<>(SB), X5 + MOVOU ·iv2<>(SB), X6 + + PXOR X8, X6 + MOVO 0(R10), X7 + + LOAD_MSG(X8, X9, X10, X11, SI, 0, 2, 4, 6, 1, 3, 5, 7) + MOVO X8, 16(R10) + MOVO X9, 32(R10) + MOVO X10, 48(R10) + MOVO X11, 64(R10) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, SI, 8, 10, 12, 14, 9, 11, 13, 15) + MOVO X8, 80(R10) + MOVO X9, 96(R10) + MOVO X10, 112(R10) + MOVO X11, 128(R10) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + LOAD_MSG(X8, X9, X10, X11, SI, 14, 4, 9, 13, 10, 8, 15, 6) + MOVO X8, 144(R10) + MOVO X9, 160(R10) + MOVO X10, 176(R10) + MOVO X11, 192(R10) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, SI, 1, 0, 11, 5, 12, 2, 7, 3) + MOVO X8, 208(R10) + MOVO X9, 224(R10) + MOVO X10, 240(R10) + MOVO X11, 256(R10) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + LOAD_MSG(X8, X9, X10, X11, SI, 11, 12, 5, 15, 8, 0, 2, 13) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, SI, 10, 3, 7, 9, 14, 6, 1, 4) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + LOAD_MSG(X8, X9, X10, X11, SI, 7, 3, 13, 11, 9, 1, 12, 14) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, SI, 2, 5, 4, 15, 6, 10, 0, 8) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + LOAD_MSG(X8, X9, X10, X11, SI, 9, 5, 2, 10, 0, 7, 4, 15) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, SI, 14, 11, 6, 3, 1, 12, 8, 13) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + LOAD_MSG(X8, X9, X10, X11, SI, 2, 6, 0, 8, 12, 10, 11, 3) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, SI, 4, 7, 15, 1, 13, 5, 14, 9) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + LOAD_MSG(X8, X9, X10, X11, SI, 12, 1, 14, 4, 5, 15, 13, 10) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, SI, 0, 6, 9, 8, 7, 3, 2, 11) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + LOAD_MSG(X8, X9, X10, X11, SI, 13, 7, 12, 3, 11, 14, 1, 9) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, SI, 5, 15, 8, 2, 0, 4, 6, 10) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + LOAD_MSG(X8, X9, X10, X11, SI, 6, 14, 11, 0, 15, 9, 3, 8) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, SI, 12, 13, 1, 10, 2, 7, 4, 5) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + LOAD_MSG(X8, X9, X10, X11, SI, 10, 8, 7, 1, 2, 4, 6, 5) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + LOAD_MSG(X8, X9, X10, X11, SI, 15, 9, 3, 13, 11, 14, 12, 0) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 16(R10), 32(R10), 48(R10), 64(R10), X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 80(R10), 96(R10), 112(R10), 128(R10), X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 144(R10), 160(R10), 176(R10), 192(R10), X11, X13, X14) + SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) + HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 208(R10), 224(R10), 240(R10), 256(R10), X11, X13, X14) + SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + + MOVOU 32(AX), X10 + MOVOU 48(AX), X11 + PXOR X0, X12 + PXOR X1, X15 + PXOR X2, X10 + PXOR X3, X11 + PXOR X4, X12 + PXOR X5, X15 + PXOR X6, X10 + PXOR X7, X11 + MOVOU X10, 32(AX) + MOVOU X11, 48(AX) + + LEAQ 128(SI), SI + SUBQ $128, DI + JNE loop + + MOVOU X12, 0(AX) + MOVOU X15, 16(AX) + + MOVQ R8, 0(BX) + MOVQ R9, 8(BX) + + RET diff --git a/vendor/golang.org/x/crypto/blake2b/blake2b_generic.go b/vendor/golang.org/x/crypto/blake2b/blake2b_generic.go new file mode 100644 index 00000000000..3168a8aa3c8 --- /dev/null +++ b/vendor/golang.org/x/crypto/blake2b/blake2b_generic.go @@ -0,0 +1,182 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blake2b + +import ( + "encoding/binary" + "math/bits" +) + +// the precomputed values for BLAKE2b +// there are 12 16-byte arrays - one for each round +// the entries are calculated from the sigma constants. +var precomputed = [12][16]byte{ + {0, 2, 4, 6, 1, 3, 5, 7, 8, 10, 12, 14, 9, 11, 13, 15}, + {14, 4, 9, 13, 10, 8, 15, 6, 1, 0, 11, 5, 12, 2, 7, 3}, + {11, 12, 5, 15, 8, 0, 2, 13, 10, 3, 7, 9, 14, 6, 1, 4}, + {7, 3, 13, 11, 9, 1, 12, 14, 2, 5, 4, 15, 6, 10, 0, 8}, + {9, 5, 2, 10, 0, 7, 4, 15, 14, 11, 6, 3, 1, 12, 8, 13}, + {2, 6, 0, 8, 12, 10, 11, 3, 4, 7, 15, 1, 13, 5, 14, 9}, + {12, 1, 14, 4, 5, 15, 13, 10, 0, 6, 9, 8, 7, 3, 2, 11}, + {13, 7, 12, 3, 11, 14, 1, 9, 5, 15, 8, 2, 0, 4, 6, 10}, + {6, 14, 11, 0, 15, 9, 3, 8, 12, 13, 1, 10, 2, 7, 4, 5}, + {10, 8, 7, 1, 2, 4, 6, 5, 15, 9, 3, 13, 11, 14, 12, 0}, + {0, 2, 4, 6, 1, 3, 5, 7, 8, 10, 12, 14, 9, 11, 13, 15}, // equal to the first + {14, 4, 9, 13, 10, 8, 15, 6, 1, 0, 11, 5, 12, 2, 7, 3}, // equal to the second +} + +func hashBlocksGeneric(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) { + var m [16]uint64 + c0, c1 := c[0], c[1] + + for i := 0; i < len(blocks); { + c0 += BlockSize + if c0 < BlockSize { + c1++ + } + + v0, v1, v2, v3, v4, v5, v6, v7 := h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7] + v8, v9, v10, v11, v12, v13, v14, v15 := iv[0], iv[1], iv[2], iv[3], iv[4], iv[5], iv[6], iv[7] + v12 ^= c0 + v13 ^= c1 + v14 ^= flag + + for j := range m { + m[j] = binary.LittleEndian.Uint64(blocks[i:]) + i += 8 + } + + for j := range precomputed { + s := &(precomputed[j]) + + v0 += m[s[0]] + v0 += v4 + v12 ^= v0 + v12 = bits.RotateLeft64(v12, -32) + v8 += v12 + v4 ^= v8 + v4 = bits.RotateLeft64(v4, -24) + v1 += m[s[1]] + v1 += v5 + v13 ^= v1 + v13 = bits.RotateLeft64(v13, -32) + v9 += v13 + v5 ^= v9 + v5 = bits.RotateLeft64(v5, -24) + v2 += m[s[2]] + v2 += v6 + v14 ^= v2 + v14 = bits.RotateLeft64(v14, -32) + v10 += v14 + v6 ^= v10 + v6 = bits.RotateLeft64(v6, -24) + v3 += m[s[3]] + v3 += v7 + v15 ^= v3 + v15 = bits.RotateLeft64(v15, -32) + v11 += v15 + v7 ^= v11 + v7 = bits.RotateLeft64(v7, -24) + + v0 += m[s[4]] + v0 += v4 + v12 ^= v0 + v12 = bits.RotateLeft64(v12, -16) + v8 += v12 + v4 ^= v8 + v4 = bits.RotateLeft64(v4, -63) + v1 += m[s[5]] + v1 += v5 + v13 ^= v1 + v13 = bits.RotateLeft64(v13, -16) + v9 += v13 + v5 ^= v9 + v5 = bits.RotateLeft64(v5, -63) + v2 += m[s[6]] + v2 += v6 + v14 ^= v2 + v14 = bits.RotateLeft64(v14, -16) + v10 += v14 + v6 ^= v10 + v6 = bits.RotateLeft64(v6, -63) + v3 += m[s[7]] + v3 += v7 + v15 ^= v3 + v15 = bits.RotateLeft64(v15, -16) + v11 += v15 + v7 ^= v11 + v7 = bits.RotateLeft64(v7, -63) + + v0 += m[s[8]] + v0 += v5 + v15 ^= v0 + v15 = bits.RotateLeft64(v15, -32) + v10 += v15 + v5 ^= v10 + v5 = bits.RotateLeft64(v5, -24) + v1 += m[s[9]] + v1 += v6 + v12 ^= v1 + v12 = bits.RotateLeft64(v12, -32) + v11 += v12 + v6 ^= v11 + v6 = bits.RotateLeft64(v6, -24) + v2 += m[s[10]] + v2 += v7 + v13 ^= v2 + v13 = bits.RotateLeft64(v13, -32) + v8 += v13 + v7 ^= v8 + v7 = bits.RotateLeft64(v7, -24) + v3 += m[s[11]] + v3 += v4 + v14 ^= v3 + v14 = bits.RotateLeft64(v14, -32) + v9 += v14 + v4 ^= v9 + v4 = bits.RotateLeft64(v4, -24) + + v0 += m[s[12]] + v0 += v5 + v15 ^= v0 + v15 = bits.RotateLeft64(v15, -16) + v10 += v15 + v5 ^= v10 + v5 = bits.RotateLeft64(v5, -63) + v1 += m[s[13]] + v1 += v6 + v12 ^= v1 + v12 = bits.RotateLeft64(v12, -16) + v11 += v12 + v6 ^= v11 + v6 = bits.RotateLeft64(v6, -63) + v2 += m[s[14]] + v2 += v7 + v13 ^= v2 + v13 = bits.RotateLeft64(v13, -16) + v8 += v13 + v7 ^= v8 + v7 = bits.RotateLeft64(v7, -63) + v3 += m[s[15]] + v3 += v4 + v14 ^= v3 + v14 = bits.RotateLeft64(v14, -16) + v9 += v14 + v4 ^= v9 + v4 = bits.RotateLeft64(v4, -63) + + } + + h[0] ^= v0 ^ v8 + h[1] ^= v1 ^ v9 + h[2] ^= v2 ^ v10 + h[3] ^= v3 ^ v11 + h[4] ^= v4 ^ v12 + h[5] ^= v5 ^ v13 + h[6] ^= v6 ^ v14 + h[7] ^= v7 ^ v15 + } + c[0], c[1] = c0, c1 +} diff --git a/vendor/golang.org/x/crypto/blake2b/blake2b_ref.go b/vendor/golang.org/x/crypto/blake2b/blake2b_ref.go new file mode 100644 index 00000000000..b0137cdf025 --- /dev/null +++ b/vendor/golang.org/x/crypto/blake2b/blake2b_ref.go @@ -0,0 +1,12 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !amd64 || purego || !gc +// +build !amd64 purego !gc + +package blake2b + +func hashBlocks(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) { + hashBlocksGeneric(h, c, flag, blocks) +} diff --git a/vendor/golang.org/x/crypto/blake2b/blake2x.go b/vendor/golang.org/x/crypto/blake2b/blake2x.go new file mode 100644 index 00000000000..52c414db0e6 --- /dev/null +++ b/vendor/golang.org/x/crypto/blake2b/blake2x.go @@ -0,0 +1,177 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package blake2b + +import ( + "encoding/binary" + "errors" + "io" +) + +// XOF defines the interface to hash functions that +// support arbitrary-length output. +type XOF interface { + // Write absorbs more data into the hash's state. It panics if called + // after Read. + io.Writer + + // Read reads more output from the hash. It returns io.EOF if the limit + // has been reached. + io.Reader + + // Clone returns a copy of the XOF in its current state. + Clone() XOF + + // Reset resets the XOF to its initial state. + Reset() +} + +// OutputLengthUnknown can be used as the size argument to NewXOF to indicate +// the length of the output is not known in advance. +const OutputLengthUnknown = 0 + +// magicUnknownOutputLength is a magic value for the output size that indicates +// an unknown number of output bytes. +const magicUnknownOutputLength = (1 << 32) - 1 + +// maxOutputLength is the absolute maximum number of bytes to produce when the +// number of output bytes is unknown. +const maxOutputLength = (1 << 32) * 64 + +// NewXOF creates a new variable-output-length hash. The hash either produce a +// known number of bytes (1 <= size < 2**32-1), or an unknown number of bytes +// (size == OutputLengthUnknown). In the latter case, an absolute limit of +// 256GiB applies. +// +// A non-nil key turns the hash into a MAC. The key must between +// zero and 32 bytes long. +func NewXOF(size uint32, key []byte) (XOF, error) { + if len(key) > Size { + return nil, errKeySize + } + if size == magicUnknownOutputLength { + // 2^32-1 indicates an unknown number of bytes and thus isn't a + // valid length. + return nil, errors.New("blake2b: XOF length too large") + } + if size == OutputLengthUnknown { + size = magicUnknownOutputLength + } + x := &xof{ + d: digest{ + size: Size, + keyLen: len(key), + }, + length: size, + } + copy(x.d.key[:], key) + x.Reset() + return x, nil +} + +type xof struct { + d digest + length uint32 + remaining uint64 + cfg, root, block [Size]byte + offset int + nodeOffset uint32 + readMode bool +} + +func (x *xof) Write(p []byte) (n int, err error) { + if x.readMode { + panic("blake2b: write to XOF after read") + } + return x.d.Write(p) +} + +func (x *xof) Clone() XOF { + clone := *x + return &clone +} + +func (x *xof) Reset() { + x.cfg[0] = byte(Size) + binary.LittleEndian.PutUint32(x.cfg[4:], uint32(Size)) // leaf length + binary.LittleEndian.PutUint32(x.cfg[12:], x.length) // XOF length + x.cfg[17] = byte(Size) // inner hash size + + x.d.Reset() + x.d.h[1] ^= uint64(x.length) << 32 + + x.remaining = uint64(x.length) + if x.remaining == magicUnknownOutputLength { + x.remaining = maxOutputLength + } + x.offset, x.nodeOffset = 0, 0 + x.readMode = false +} + +func (x *xof) Read(p []byte) (n int, err error) { + if !x.readMode { + x.d.finalize(&x.root) + x.readMode = true + } + + if x.remaining == 0 { + return 0, io.EOF + } + + n = len(p) + if uint64(n) > x.remaining { + n = int(x.remaining) + p = p[:n] + } + + if x.offset > 0 { + blockRemaining := Size - x.offset + if n < blockRemaining { + x.offset += copy(p, x.block[x.offset:]) + x.remaining -= uint64(n) + return + } + copy(p, x.block[x.offset:]) + p = p[blockRemaining:] + x.offset = 0 + x.remaining -= uint64(blockRemaining) + } + + for len(p) >= Size { + binary.LittleEndian.PutUint32(x.cfg[8:], x.nodeOffset) + x.nodeOffset++ + + x.d.initConfig(&x.cfg) + x.d.Write(x.root[:]) + x.d.finalize(&x.block) + + copy(p, x.block[:]) + p = p[Size:] + x.remaining -= uint64(Size) + } + + if todo := len(p); todo > 0 { + if x.remaining < uint64(Size) { + x.cfg[0] = byte(x.remaining) + } + binary.LittleEndian.PutUint32(x.cfg[8:], x.nodeOffset) + x.nodeOffset++ + + x.d.initConfig(&x.cfg) + x.d.Write(x.root[:]) + x.d.finalize(&x.block) + + x.offset = copy(p, x.block[:todo]) + x.remaining -= uint64(todo) + } + return +} + +func (d *digest) initConfig(cfg *[Size]byte) { + d.offset, d.c[0], d.c[1] = 0, 0, 0 + for i := range d.h { + d.h[i] = iv[i] ^ binary.LittleEndian.Uint64(cfg[i*8:]) + } +} diff --git a/vendor/golang.org/x/crypto/blake2b/register.go b/vendor/golang.org/x/crypto/blake2b/register.go new file mode 100644 index 00000000000..9d8633963cb --- /dev/null +++ b/vendor/golang.org/x/crypto/blake2b/register.go @@ -0,0 +1,33 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.9 +// +build go1.9 + +package blake2b + +import ( + "crypto" + "hash" +) + +func init() { + newHash256 := func() hash.Hash { + h, _ := New256(nil) + return h + } + newHash384 := func() hash.Hash { + h, _ := New384(nil) + return h + } + + newHash512 := func() hash.Hash { + h, _ := New512(nil) + return h + } + + crypto.RegisterHash(crypto.BLAKE2b_256, newHash256) + crypto.RegisterHash(crypto.BLAKE2b_384, newHash384) + crypto.RegisterHash(crypto.BLAKE2b_512, newHash512) +} diff --git a/vendor/golang.org/x/crypto/ripemd160/ripemd160.go b/vendor/golang.org/x/crypto/ripemd160/ripemd160.go new file mode 100644 index 00000000000..cf3eeb158a9 --- /dev/null +++ b/vendor/golang.org/x/crypto/ripemd160/ripemd160.go @@ -0,0 +1,124 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package ripemd160 implements the RIPEMD-160 hash algorithm. +// +// Deprecated: RIPEMD-160 is a legacy hash and should not be used for new +// applications. Also, this package does not and will not provide an optimized +// implementation. Instead, use a modern hash like SHA-256 (from crypto/sha256). +package ripemd160 // import "golang.org/x/crypto/ripemd160" + +// RIPEMD-160 is designed by Hans Dobbertin, Antoon Bosselaers, and Bart +// Preneel with specifications available at: +// http://homes.esat.kuleuven.be/~cosicart/pdf/AB-9601/AB-9601.pdf. + +import ( + "crypto" + "hash" +) + +func init() { + crypto.RegisterHash(crypto.RIPEMD160, New) +} + +// The size of the checksum in bytes. +const Size = 20 + +// The block size of the hash algorithm in bytes. +const BlockSize = 64 + +const ( + _s0 = 0x67452301 + _s1 = 0xefcdab89 + _s2 = 0x98badcfe + _s3 = 0x10325476 + _s4 = 0xc3d2e1f0 +) + +// digest represents the partial evaluation of a checksum. +type digest struct { + s [5]uint32 // running context + x [BlockSize]byte // temporary buffer + nx int // index into x + tc uint64 // total count of bytes processed +} + +func (d *digest) Reset() { + d.s[0], d.s[1], d.s[2], d.s[3], d.s[4] = _s0, _s1, _s2, _s3, _s4 + d.nx = 0 + d.tc = 0 +} + +// New returns a new hash.Hash computing the checksum. +func New() hash.Hash { + result := new(digest) + result.Reset() + return result +} + +func (d *digest) Size() int { return Size } + +func (d *digest) BlockSize() int { return BlockSize } + +func (d *digest) Write(p []byte) (nn int, err error) { + nn = len(p) + d.tc += uint64(nn) + if d.nx > 0 { + n := len(p) + if n > BlockSize-d.nx { + n = BlockSize - d.nx + } + for i := 0; i < n; i++ { + d.x[d.nx+i] = p[i] + } + d.nx += n + if d.nx == BlockSize { + _Block(d, d.x[0:]) + d.nx = 0 + } + p = p[n:] + } + n := _Block(d, p) + p = p[n:] + if len(p) > 0 { + d.nx = copy(d.x[:], p) + } + return +} + +func (d0 *digest) Sum(in []byte) []byte { + // Make a copy of d0 so that caller can keep writing and summing. + d := *d0 + + // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64. + tc := d.tc + var tmp [64]byte + tmp[0] = 0x80 + if tc%64 < 56 { + d.Write(tmp[0 : 56-tc%64]) + } else { + d.Write(tmp[0 : 64+56-tc%64]) + } + + // Length in bits. + tc <<= 3 + for i := uint(0); i < 8; i++ { + tmp[i] = byte(tc >> (8 * i)) + } + d.Write(tmp[0:8]) + + if d.nx != 0 { + panic("d.nx != 0") + } + + var digest [Size]byte + for i, s := range d.s { + digest[i*4] = byte(s) + digest[i*4+1] = byte(s >> 8) + digest[i*4+2] = byte(s >> 16) + digest[i*4+3] = byte(s >> 24) + } + + return append(in, digest[:]...) +} diff --git a/vendor/golang.org/x/crypto/ripemd160/ripemd160block.go b/vendor/golang.org/x/crypto/ripemd160/ripemd160block.go new file mode 100644 index 00000000000..e0edc02f0f3 --- /dev/null +++ b/vendor/golang.org/x/crypto/ripemd160/ripemd160block.go @@ -0,0 +1,165 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// RIPEMD-160 block step. +// In its own file so that a faster assembly or C version +// can be substituted easily. + +package ripemd160 + +import ( + "math/bits" +) + +// work buffer indices and roll amounts for one line +var _n = [80]uint{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13, +} + +var _r = [80]uint{ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6, +} + +// same for the other parallel one +var n_ = [80]uint{ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11, +} + +var r_ = [80]uint{ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11, +} + +func _Block(md *digest, p []byte) int { + n := 0 + var x [16]uint32 + var alpha, beta uint32 + for len(p) >= BlockSize { + a, b, c, d, e := md.s[0], md.s[1], md.s[2], md.s[3], md.s[4] + aa, bb, cc, dd, ee := a, b, c, d, e + j := 0 + for i := 0; i < 16; i++ { + x[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24 + j += 4 + } + + // round 1 + i := 0 + for i < 16 { + alpha = a + (b ^ c ^ d) + x[_n[i]] + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb ^ (cc | ^dd)) + x[n_[i]] + 0x50a28be6 + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 2 + for i < 32 { + alpha = a + (b&c | ^b&d) + x[_n[i]] + 0x5a827999 + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb&dd | cc&^dd) + x[n_[i]] + 0x5c4dd124 + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 3 + for i < 48 { + alpha = a + (b | ^c ^ d) + x[_n[i]] + 0x6ed9eba1 + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb | ^cc ^ dd) + x[n_[i]] + 0x6d703ef3 + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 4 + for i < 64 { + alpha = a + (b&d | c&^d) + x[_n[i]] + 0x8f1bbcdc + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb&cc | ^bb&dd) + x[n_[i]] + 0x7a6d76e9 + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 5 + for i < 80 { + alpha = a + (b ^ (c | ^d)) + x[_n[i]] + 0xa953fd4e + s := int(_r[i]) + alpha = bits.RotateLeft32(alpha, s) + e + beta = bits.RotateLeft32(c, 10) + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb ^ cc ^ dd) + x[n_[i]] + s = int(r_[i]) + alpha = bits.RotateLeft32(alpha, s) + ee + beta = bits.RotateLeft32(cc, 10) + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // combine results + dd += c + md.s[1] + md.s[1] = md.s[2] + d + ee + md.s[2] = md.s[3] + e + aa + md.s[3] = md.s[4] + a + bb + md.s[4] = md.s[0] + b + cc + md.s[0] = dd + + p = p[BlockSize:] + n += BlockSize + } + return n +} diff --git a/vendor/golang.org/x/crypto/twofish/twofish.go b/vendor/golang.org/x/crypto/twofish/twofish.go new file mode 100644 index 00000000000..e4eeae17f40 --- /dev/null +++ b/vendor/golang.org/x/crypto/twofish/twofish.go @@ -0,0 +1,341 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package twofish implements Bruce Schneier's Twofish encryption algorithm. +// +// Deprecated: Twofish is a legacy cipher and should not be used for new +// applications. Also, this package does not and will not provide an optimized +// implementation. Instead, use AES (from crypto/aes, if necessary in an AEAD +// mode like crypto/cipher.NewGCM) or XChaCha20-Poly1305 (from +// golang.org/x/crypto/chacha20poly1305). +package twofish // import "golang.org/x/crypto/twofish" + +// Twofish is defined in https://www.schneier.com/paper-twofish-paper.pdf [TWOFISH] + +// This code is a port of the LibTom C implementation. +// See http://libtom.org/?page=features&newsitems=5&whatfile=crypt. +// LibTomCrypt is free for all purposes under the public domain. +// It was heavily inspired by the go blowfish package. + +import ( + "math/bits" + "strconv" +) + +// BlockSize is the constant block size of Twofish. +const BlockSize = 16 + +const mdsPolynomial = 0x169 // x^8 + x^6 + x^5 + x^3 + 1, see [TWOFISH] 4.2 +const rsPolynomial = 0x14d // x^8 + x^6 + x^3 + x^2 + 1, see [TWOFISH] 4.3 + +// A Cipher is an instance of Twofish encryption using a particular key. +type Cipher struct { + s [4][256]uint32 + k [40]uint32 +} + +type KeySizeError int + +func (k KeySizeError) Error() string { + return "crypto/twofish: invalid key size " + strconv.Itoa(int(k)) +} + +// NewCipher creates and returns a Cipher. +// The key argument should be the Twofish key, 16, 24 or 32 bytes. +func NewCipher(key []byte) (*Cipher, error) { + keylen := len(key) + + if keylen != 16 && keylen != 24 && keylen != 32 { + return nil, KeySizeError(keylen) + } + + // k is the number of 64 bit words in key + k := keylen / 8 + + // Create the S[..] words + var S [4 * 4]byte + for i := 0; i < k; i++ { + // Computes [y0 y1 y2 y3] = rs . [x0 x1 x2 x3 x4 x5 x6 x7] + for j, rsRow := range rs { + for k, rsVal := range rsRow { + S[4*i+j] ^= gfMult(key[8*i+k], rsVal, rsPolynomial) + } + } + } + + // Calculate subkeys + c := new(Cipher) + var tmp [4]byte + for i := byte(0); i < 20; i++ { + // A = h(p * 2x, Me) + for j := range tmp { + tmp[j] = 2 * i + } + A := h(tmp[:], key, 0) + + // B = rolc(h(p * (2x + 1), Mo), 8) + for j := range tmp { + tmp[j] = 2*i + 1 + } + B := h(tmp[:], key, 1) + B = bits.RotateLeft32(B, 8) + + c.k[2*i] = A + B + + // K[2i+1] = (A + 2B) <<< 9 + c.k[2*i+1] = bits.RotateLeft32(2*B+A, 9) + } + + // Calculate sboxes + switch k { + case 2: + for i := range c.s[0] { + c.s[0][i] = mdsColumnMult(sbox[1][sbox[0][sbox[0][byte(i)]^S[0]]^S[4]], 0) + c.s[1][i] = mdsColumnMult(sbox[0][sbox[0][sbox[1][byte(i)]^S[1]]^S[5]], 1) + c.s[2][i] = mdsColumnMult(sbox[1][sbox[1][sbox[0][byte(i)]^S[2]]^S[6]], 2) + c.s[3][i] = mdsColumnMult(sbox[0][sbox[1][sbox[1][byte(i)]^S[3]]^S[7]], 3) + } + case 3: + for i := range c.s[0] { + c.s[0][i] = mdsColumnMult(sbox[1][sbox[0][sbox[0][sbox[1][byte(i)]^S[0]]^S[4]]^S[8]], 0) + c.s[1][i] = mdsColumnMult(sbox[0][sbox[0][sbox[1][sbox[1][byte(i)]^S[1]]^S[5]]^S[9]], 1) + c.s[2][i] = mdsColumnMult(sbox[1][sbox[1][sbox[0][sbox[0][byte(i)]^S[2]]^S[6]]^S[10]], 2) + c.s[3][i] = mdsColumnMult(sbox[0][sbox[1][sbox[1][sbox[0][byte(i)]^S[3]]^S[7]]^S[11]], 3) + } + default: + for i := range c.s[0] { + c.s[0][i] = mdsColumnMult(sbox[1][sbox[0][sbox[0][sbox[1][sbox[1][byte(i)]^S[0]]^S[4]]^S[8]]^S[12]], 0) + c.s[1][i] = mdsColumnMult(sbox[0][sbox[0][sbox[1][sbox[1][sbox[0][byte(i)]^S[1]]^S[5]]^S[9]]^S[13]], 1) + c.s[2][i] = mdsColumnMult(sbox[1][sbox[1][sbox[0][sbox[0][sbox[0][byte(i)]^S[2]]^S[6]]^S[10]]^S[14]], 2) + c.s[3][i] = mdsColumnMult(sbox[0][sbox[1][sbox[1][sbox[0][sbox[1][byte(i)]^S[3]]^S[7]]^S[11]]^S[15]], 3) + } + } + + return c, nil +} + +// BlockSize returns the Twofish block size, 16 bytes. +func (c *Cipher) BlockSize() int { return BlockSize } + +// store32l stores src in dst in little-endian form. +func store32l(dst []byte, src uint32) { + dst[0] = byte(src) + dst[1] = byte(src >> 8) + dst[2] = byte(src >> 16) + dst[3] = byte(src >> 24) + return +} + +// load32l reads a little-endian uint32 from src. +func load32l(src []byte) uint32 { + return uint32(src[0]) | uint32(src[1])<<8 | uint32(src[2])<<16 | uint32(src[3])<<24 +} + +// The RS matrix. See [TWOFISH] 4.3 +var rs = [4][8]byte{ + {0x01, 0xA4, 0x55, 0x87, 0x5A, 0x58, 0xDB, 0x9E}, + {0xA4, 0x56, 0x82, 0xF3, 0x1E, 0xC6, 0x68, 0xE5}, + {0x02, 0xA1, 0xFC, 0xC1, 0x47, 0xAE, 0x3D, 0x19}, + {0xA4, 0x55, 0x87, 0x5A, 0x58, 0xDB, 0x9E, 0x03}, +} + +// sbox tables +var sbox = [2][256]byte{ + { + 0xa9, 0x67, 0xb3, 0xe8, 0x04, 0xfd, 0xa3, 0x76, 0x9a, 0x92, 0x80, 0x78, 0xe4, 0xdd, 0xd1, 0x38, + 0x0d, 0xc6, 0x35, 0x98, 0x18, 0xf7, 0xec, 0x6c, 0x43, 0x75, 0x37, 0x26, 0xfa, 0x13, 0x94, 0x48, + 0xf2, 0xd0, 0x8b, 0x30, 0x84, 0x54, 0xdf, 0x23, 0x19, 0x5b, 0x3d, 0x59, 0xf3, 0xae, 0xa2, 0x82, + 0x63, 0x01, 0x83, 0x2e, 0xd9, 0x51, 0x9b, 0x7c, 0xa6, 0xeb, 0xa5, 0xbe, 0x16, 0x0c, 0xe3, 0x61, + 0xc0, 0x8c, 0x3a, 0xf5, 0x73, 0x2c, 0x25, 0x0b, 0xbb, 0x4e, 0x89, 0x6b, 0x53, 0x6a, 0xb4, 0xf1, + 0xe1, 0xe6, 0xbd, 0x45, 0xe2, 0xf4, 0xb6, 0x66, 0xcc, 0x95, 0x03, 0x56, 0xd4, 0x1c, 0x1e, 0xd7, + 0xfb, 0xc3, 0x8e, 0xb5, 0xe9, 0xcf, 0xbf, 0xba, 0xea, 0x77, 0x39, 0xaf, 0x33, 0xc9, 0x62, 0x71, + 0x81, 0x79, 0x09, 0xad, 0x24, 0xcd, 0xf9, 0xd8, 0xe5, 0xc5, 0xb9, 0x4d, 0x44, 0x08, 0x86, 0xe7, + 0xa1, 0x1d, 0xaa, 0xed, 0x06, 0x70, 0xb2, 0xd2, 0x41, 0x7b, 0xa0, 0x11, 0x31, 0xc2, 0x27, 0x90, + 0x20, 0xf6, 0x60, 0xff, 0x96, 0x5c, 0xb1, 0xab, 0x9e, 0x9c, 0x52, 0x1b, 0x5f, 0x93, 0x0a, 0xef, + 0x91, 0x85, 0x49, 0xee, 0x2d, 0x4f, 0x8f, 0x3b, 0x47, 0x87, 0x6d, 0x46, 0xd6, 0x3e, 0x69, 0x64, + 0x2a, 0xce, 0xcb, 0x2f, 0xfc, 0x97, 0x05, 0x7a, 0xac, 0x7f, 0xd5, 0x1a, 0x4b, 0x0e, 0xa7, 0x5a, + 0x28, 0x14, 0x3f, 0x29, 0x88, 0x3c, 0x4c, 0x02, 0xb8, 0xda, 0xb0, 0x17, 0x55, 0x1f, 0x8a, 0x7d, + 0x57, 0xc7, 0x8d, 0x74, 0xb7, 0xc4, 0x9f, 0x72, 0x7e, 0x15, 0x22, 0x12, 0x58, 0x07, 0x99, 0x34, + 0x6e, 0x50, 0xde, 0x68, 0x65, 0xbc, 0xdb, 0xf8, 0xc8, 0xa8, 0x2b, 0x40, 0xdc, 0xfe, 0x32, 0xa4, + 0xca, 0x10, 0x21, 0xf0, 0xd3, 0x5d, 0x0f, 0x00, 0x6f, 0x9d, 0x36, 0x42, 0x4a, 0x5e, 0xc1, 0xe0, + }, + { + 0x75, 0xf3, 0xc6, 0xf4, 0xdb, 0x7b, 0xfb, 0xc8, 0x4a, 0xd3, 0xe6, 0x6b, 0x45, 0x7d, 0xe8, 0x4b, + 0xd6, 0x32, 0xd8, 0xfd, 0x37, 0x71, 0xf1, 0xe1, 0x30, 0x0f, 0xf8, 0x1b, 0x87, 0xfa, 0x06, 0x3f, + 0x5e, 0xba, 0xae, 0x5b, 0x8a, 0x00, 0xbc, 0x9d, 0x6d, 0xc1, 0xb1, 0x0e, 0x80, 0x5d, 0xd2, 0xd5, + 0xa0, 0x84, 0x07, 0x14, 0xb5, 0x90, 0x2c, 0xa3, 0xb2, 0x73, 0x4c, 0x54, 0x92, 0x74, 0x36, 0x51, + 0x38, 0xb0, 0xbd, 0x5a, 0xfc, 0x60, 0x62, 0x96, 0x6c, 0x42, 0xf7, 0x10, 0x7c, 0x28, 0x27, 0x8c, + 0x13, 0x95, 0x9c, 0xc7, 0x24, 0x46, 0x3b, 0x70, 0xca, 0xe3, 0x85, 0xcb, 0x11, 0xd0, 0x93, 0xb8, + 0xa6, 0x83, 0x20, 0xff, 0x9f, 0x77, 0xc3, 0xcc, 0x03, 0x6f, 0x08, 0xbf, 0x40, 0xe7, 0x2b, 0xe2, + 0x79, 0x0c, 0xaa, 0x82, 0x41, 0x3a, 0xea, 0xb9, 0xe4, 0x9a, 0xa4, 0x97, 0x7e, 0xda, 0x7a, 0x17, + 0x66, 0x94, 0xa1, 0x1d, 0x3d, 0xf0, 0xde, 0xb3, 0x0b, 0x72, 0xa7, 0x1c, 0xef, 0xd1, 0x53, 0x3e, + 0x8f, 0x33, 0x26, 0x5f, 0xec, 0x76, 0x2a, 0x49, 0x81, 0x88, 0xee, 0x21, 0xc4, 0x1a, 0xeb, 0xd9, + 0xc5, 0x39, 0x99, 0xcd, 0xad, 0x31, 0x8b, 0x01, 0x18, 0x23, 0xdd, 0x1f, 0x4e, 0x2d, 0xf9, 0x48, + 0x4f, 0xf2, 0x65, 0x8e, 0x78, 0x5c, 0x58, 0x19, 0x8d, 0xe5, 0x98, 0x57, 0x67, 0x7f, 0x05, 0x64, + 0xaf, 0x63, 0xb6, 0xfe, 0xf5, 0xb7, 0x3c, 0xa5, 0xce, 0xe9, 0x68, 0x44, 0xe0, 0x4d, 0x43, 0x69, + 0x29, 0x2e, 0xac, 0x15, 0x59, 0xa8, 0x0a, 0x9e, 0x6e, 0x47, 0xdf, 0x34, 0x35, 0x6a, 0xcf, 0xdc, + 0x22, 0xc9, 0xc0, 0x9b, 0x89, 0xd4, 0xed, 0xab, 0x12, 0xa2, 0x0d, 0x52, 0xbb, 0x02, 0x2f, 0xa9, + 0xd7, 0x61, 0x1e, 0xb4, 0x50, 0x04, 0xf6, 0xc2, 0x16, 0x25, 0x86, 0x56, 0x55, 0x09, 0xbe, 0x91, + }, +} + +// gfMult returns a·b in GF(2^8)/p +func gfMult(a, b byte, p uint32) byte { + B := [2]uint32{0, uint32(b)} + P := [2]uint32{0, p} + var result uint32 + + // branchless GF multiplier + for i := 0; i < 7; i++ { + result ^= B[a&1] + a >>= 1 + B[1] = P[B[1]>>7] ^ (B[1] << 1) + } + result ^= B[a&1] + return byte(result) +} + +// mdsColumnMult calculates y{col} where [y0 y1 y2 y3] = MDS · [x0] +func mdsColumnMult(in byte, col int) uint32 { + mul01 := in + mul5B := gfMult(in, 0x5B, mdsPolynomial) + mulEF := gfMult(in, 0xEF, mdsPolynomial) + + switch col { + case 0: + return uint32(mul01) | uint32(mul5B)<<8 | uint32(mulEF)<<16 | uint32(mulEF)<<24 + case 1: + return uint32(mulEF) | uint32(mulEF)<<8 | uint32(mul5B)<<16 | uint32(mul01)<<24 + case 2: + return uint32(mul5B) | uint32(mulEF)<<8 | uint32(mul01)<<16 | uint32(mulEF)<<24 + case 3: + return uint32(mul5B) | uint32(mul01)<<8 | uint32(mulEF)<<16 | uint32(mul5B)<<24 + } + + panic("unreachable") +} + +// h implements the S-box generation function. See [TWOFISH] 4.3.5 +func h(in, key []byte, offset int) uint32 { + var y [4]byte + for x := range y { + y[x] = in[x] + } + switch len(key) / 8 { + case 4: + y[0] = sbox[1][y[0]] ^ key[4*(6+offset)+0] + y[1] = sbox[0][y[1]] ^ key[4*(6+offset)+1] + y[2] = sbox[0][y[2]] ^ key[4*(6+offset)+2] + y[3] = sbox[1][y[3]] ^ key[4*(6+offset)+3] + fallthrough + case 3: + y[0] = sbox[1][y[0]] ^ key[4*(4+offset)+0] + y[1] = sbox[1][y[1]] ^ key[4*(4+offset)+1] + y[2] = sbox[0][y[2]] ^ key[4*(4+offset)+2] + y[3] = sbox[0][y[3]] ^ key[4*(4+offset)+3] + fallthrough + case 2: + y[0] = sbox[1][sbox[0][sbox[0][y[0]]^key[4*(2+offset)+0]]^key[4*(0+offset)+0]] + y[1] = sbox[0][sbox[0][sbox[1][y[1]]^key[4*(2+offset)+1]]^key[4*(0+offset)+1]] + y[2] = sbox[1][sbox[1][sbox[0][y[2]]^key[4*(2+offset)+2]]^key[4*(0+offset)+2]] + y[3] = sbox[0][sbox[1][sbox[1][y[3]]^key[4*(2+offset)+3]]^key[4*(0+offset)+3]] + } + // [y0 y1 y2 y3] = MDS . [x0 x1 x2 x3] + var mdsMult uint32 + for i := range y { + mdsMult ^= mdsColumnMult(y[i], i) + } + return mdsMult +} + +// Encrypt encrypts a 16-byte block from src to dst, which may overlap. +// Note that for amounts of data larger than a block, +// it is not safe to just call Encrypt on successive blocks; +// instead, use an encryption mode like CBC (see crypto/cipher/cbc.go). +func (c *Cipher) Encrypt(dst, src []byte) { + S1 := c.s[0] + S2 := c.s[1] + S3 := c.s[2] + S4 := c.s[3] + + // Load input + ia := load32l(src[0:4]) + ib := load32l(src[4:8]) + ic := load32l(src[8:12]) + id := load32l(src[12:16]) + + // Pre-whitening + ia ^= c.k[0] + ib ^= c.k[1] + ic ^= c.k[2] + id ^= c.k[3] + + for i := 0; i < 8; i++ { + k := c.k[8+i*4 : 12+i*4] + t2 := S2[byte(ib)] ^ S3[byte(ib>>8)] ^ S4[byte(ib>>16)] ^ S1[byte(ib>>24)] + t1 := S1[byte(ia)] ^ S2[byte(ia>>8)] ^ S3[byte(ia>>16)] ^ S4[byte(ia>>24)] + t2 + ic = bits.RotateLeft32(ic^(t1+k[0]), -1) + id = bits.RotateLeft32(id, 1) ^ (t2 + t1 + k[1]) + + t2 = S2[byte(id)] ^ S3[byte(id>>8)] ^ S4[byte(id>>16)] ^ S1[byte(id>>24)] + t1 = S1[byte(ic)] ^ S2[byte(ic>>8)] ^ S3[byte(ic>>16)] ^ S4[byte(ic>>24)] + t2 + ia = bits.RotateLeft32(ia^(t1+k[2]), -1) + ib = bits.RotateLeft32(ib, 1) ^ (t2 + t1 + k[3]) + } + + // Output with "undo last swap" + ta := ic ^ c.k[4] + tb := id ^ c.k[5] + tc := ia ^ c.k[6] + td := ib ^ c.k[7] + + store32l(dst[0:4], ta) + store32l(dst[4:8], tb) + store32l(dst[8:12], tc) + store32l(dst[12:16], td) +} + +// Decrypt decrypts a 16-byte block from src to dst, which may overlap. +func (c *Cipher) Decrypt(dst, src []byte) { + S1 := c.s[0] + S2 := c.s[1] + S3 := c.s[2] + S4 := c.s[3] + + // Load input + ta := load32l(src[0:4]) + tb := load32l(src[4:8]) + tc := load32l(src[8:12]) + td := load32l(src[12:16]) + + // Undo undo final swap + ia := tc ^ c.k[6] + ib := td ^ c.k[7] + ic := ta ^ c.k[4] + id := tb ^ c.k[5] + + for i := 8; i > 0; i-- { + k := c.k[4+i*4 : 8+i*4] + t2 := S2[byte(id)] ^ S3[byte(id>>8)] ^ S4[byte(id>>16)] ^ S1[byte(id>>24)] + t1 := S1[byte(ic)] ^ S2[byte(ic>>8)] ^ S3[byte(ic>>16)] ^ S4[byte(ic>>24)] + t2 + ia = bits.RotateLeft32(ia, 1) ^ (t1 + k[2]) + ib = bits.RotateLeft32(ib^(t2+t1+k[3]), -1) + + t2 = S2[byte(ib)] ^ S3[byte(ib>>8)] ^ S4[byte(ib>>16)] ^ S1[byte(ib>>24)] + t1 = S1[byte(ia)] ^ S2[byte(ia>>8)] ^ S3[byte(ia>>16)] ^ S4[byte(ia>>24)] + t2 + ic = bits.RotateLeft32(ic, 1) ^ (t1 + k[0]) + id = bits.RotateLeft32(id^(t2+t1+k[1]), -1) + } + + // Undo pre-whitening + ia ^= c.k[0] + ib ^= c.k[1] + ic ^= c.k[2] + id ^= c.k[3] + + store32l(dst[0:4], ia) + store32l(dst[4:8], ib) + store32l(dst[8:12], ic) + store32l(dst[12:16], id) +} diff --git a/vendor/golang.org/x/crypto/xts/xts.go b/vendor/golang.org/x/crypto/xts/xts.go new file mode 100644 index 00000000000..8c16a830146 --- /dev/null +++ b/vendor/golang.org/x/crypto/xts/xts.go @@ -0,0 +1,164 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package xts implements the XTS cipher mode as specified in IEEE P1619/D16. +// +// XTS mode is typically used for disk encryption, which presents a number of +// novel problems that make more common modes inapplicable. The disk is +// conceptually an array of sectors and we must be able to encrypt and decrypt +// a sector in isolation. However, an attacker must not be able to transpose +// two sectors of plaintext by transposing their ciphertext. +// +// XTS wraps a block cipher with Rogaway's XEX mode in order to build a +// tweakable block cipher. This allows each sector to have a unique tweak and +// effectively create a unique key for each sector. +// +// XTS does not provide any authentication. An attacker can manipulate the +// ciphertext and randomise a block (16 bytes) of the plaintext. This package +// does not implement ciphertext-stealing so sectors must be a multiple of 16 +// bytes. +// +// Note that XTS is usually not appropriate for any use besides disk encryption. +// Most users should use an AEAD mode like GCM (from crypto/cipher.NewGCM) instead. +package xts // import "golang.org/x/crypto/xts" + +import ( + "crypto/cipher" + "encoding/binary" + "errors" + "sync" + + "golang.org/x/crypto/internal/alias" +) + +// Cipher contains an expanded key structure. It is safe for concurrent use if +// the underlying block cipher is safe for concurrent use. +type Cipher struct { + k1, k2 cipher.Block +} + +// blockSize is the block size that the underlying cipher must have. XTS is +// only defined for 16-byte ciphers. +const blockSize = 16 + +var tweakPool = sync.Pool{ + New: func() interface{} { + return new([blockSize]byte) + }, +} + +// NewCipher creates a Cipher given a function for creating the underlying +// block cipher (which must have a block size of 16 bytes). The key must be +// twice the length of the underlying cipher's key. +func NewCipher(cipherFunc func([]byte) (cipher.Block, error), key []byte) (c *Cipher, err error) { + c = new(Cipher) + if c.k1, err = cipherFunc(key[:len(key)/2]); err != nil { + return + } + c.k2, err = cipherFunc(key[len(key)/2:]) + + if c.k1.BlockSize() != blockSize { + err = errors.New("xts: cipher does not have a block size of 16") + } + + return +} + +// Encrypt encrypts a sector of plaintext and puts the result into ciphertext. +// Plaintext and ciphertext must overlap entirely or not at all. +// Sectors must be a multiple of 16 bytes and less than 2²⁴ bytes. +func (c *Cipher) Encrypt(ciphertext, plaintext []byte, sectorNum uint64) { + if len(ciphertext) < len(plaintext) { + panic("xts: ciphertext is smaller than plaintext") + } + if len(plaintext)%blockSize != 0 { + panic("xts: plaintext is not a multiple of the block size") + } + if alias.InexactOverlap(ciphertext[:len(plaintext)], plaintext) { + panic("xts: invalid buffer overlap") + } + + tweak := tweakPool.Get().(*[blockSize]byte) + for i := range tweak { + tweak[i] = 0 + } + binary.LittleEndian.PutUint64(tweak[:8], sectorNum) + + c.k2.Encrypt(tweak[:], tweak[:]) + + for len(plaintext) > 0 { + for j := range tweak { + ciphertext[j] = plaintext[j] ^ tweak[j] + } + c.k1.Encrypt(ciphertext, ciphertext) + for j := range tweak { + ciphertext[j] ^= tweak[j] + } + plaintext = plaintext[blockSize:] + ciphertext = ciphertext[blockSize:] + + mul2(tweak) + } + + tweakPool.Put(tweak) +} + +// Decrypt decrypts a sector of ciphertext and puts the result into plaintext. +// Plaintext and ciphertext must overlap entirely or not at all. +// Sectors must be a multiple of 16 bytes and less than 2²⁴ bytes. +func (c *Cipher) Decrypt(plaintext, ciphertext []byte, sectorNum uint64) { + if len(plaintext) < len(ciphertext) { + panic("xts: plaintext is smaller than ciphertext") + } + if len(ciphertext)%blockSize != 0 { + panic("xts: ciphertext is not a multiple of the block size") + } + if alias.InexactOverlap(plaintext[:len(ciphertext)], ciphertext) { + panic("xts: invalid buffer overlap") + } + + tweak := tweakPool.Get().(*[blockSize]byte) + for i := range tweak { + tweak[i] = 0 + } + binary.LittleEndian.PutUint64(tweak[:8], sectorNum) + + c.k2.Encrypt(tweak[:], tweak[:]) + + for len(ciphertext) > 0 { + for j := range tweak { + plaintext[j] = ciphertext[j] ^ tweak[j] + } + c.k1.Decrypt(plaintext, plaintext) + for j := range tweak { + plaintext[j] ^= tweak[j] + } + plaintext = plaintext[blockSize:] + ciphertext = ciphertext[blockSize:] + + mul2(tweak) + } + + tweakPool.Put(tweak) +} + +// mul2 multiplies tweak by 2 in GF(2¹²⁸) with an irreducible polynomial of +// x¹²⁸ + x⁷ + x² + x + 1. +func mul2(tweak *[blockSize]byte) { + var carryIn byte + for j := range tweak { + carryOut := tweak[j] >> 7 + tweak[j] = (tweak[j] << 1) + carryIn + carryIn = carryOut + } + if carryIn != 0 { + // If we have a carry bit then we need to subtract a multiple + // of the irreducible polynomial (x¹²⁸ + x⁷ + x² + x + 1). + // By dropping the carry bit, we're subtracting the x^128 term + // so all that remains is to subtract x⁷ + x² + x + 1. + // Subtraction (and addition) in this representation is just + // XOR. + tweak[0] ^= 1<<7 | 1<<2 | 1<<1 | 1 + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index b7fec5b9368..1290cd2826b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -51,6 +51,9 @@ github.com/VividCortex/ewma # github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d ## explicit github.com/acarl005/stripansi +# github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 +## explicit +github.com/aead/serpent # github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 ## explicit; go 1.13 github.com/asaskevich/govalidator @@ -201,6 +204,9 @@ github.com/containers/image/v5/version # github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 ## explicit github.com/containers/libtrust +# github.com/containers/luksy v0.0.0-20230808154129-d2d74a56682f +## explicit; go 1.20 +github.com/containers/luksy # github.com/containers/ocicrypt v1.1.8 ## explicit; go 1.20 github.com/containers/ocicrypt @@ -689,7 +695,9 @@ go.opencensus.io/trace/internal go.opencensus.io/trace/tracestate # golang.org/x/crypto v0.13.0 ## explicit; go 1.17 +golang.org/x/crypto/argon2 golang.org/x/crypto/bcrypt +golang.org/x/crypto/blake2b golang.org/x/crypto/blowfish golang.org/x/crypto/cast5 golang.org/x/crypto/chacha20 @@ -707,12 +715,15 @@ golang.org/x/crypto/openpgp/errors golang.org/x/crypto/openpgp/packet golang.org/x/crypto/openpgp/s2k golang.org/x/crypto/pbkdf2 +golang.org/x/crypto/ripemd160 golang.org/x/crypto/salsa20/salsa golang.org/x/crypto/scrypt golang.org/x/crypto/sha3 golang.org/x/crypto/ssh golang.org/x/crypto/ssh/agent golang.org/x/crypto/ssh/internal/bcrypt_pbkdf +golang.org/x/crypto/twofish +golang.org/x/crypto/xts # golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b ## explicit; go 1.20 golang.org/x/exp/constraints