Skip to content

Add reencryption - #569

Open
frederic-hoerni wants to merge 10 commits into
masterfrom
feature/luks-reencryption
Open

frederic-hoerni wants to merge 10 commits into
masterfrom
feature/luks-reencryption

Conversation

@frederic-hoerni

@frederic-hoerni frederic-hoerni commented Sep 8, 2026 •

Copy link
Copy Markdown
Collaborator

This adds the feature of reencryption of active LUKS2 containers.

Main changes:

  • API functions:
    • ReencryptionForActiveVolume
    • Initialize
    • Resume
    • Status
  • Named tokens now may have 2 keyslots, which happens during reencryption.
  • OpenRead sets a shared lock on the LUKS2 container so that reencryption cannot happen while a StorageContainerReader is open.
  • Introduction of secboot-tool, that is a tool to test and illustrates high-level functions of secboot (mostly reencryption at the moment).

@frederic-hoerni
frederic-hoerni force-pushed the feature/luks-reencryption branch 2 times, most recently from 3815939 to 731117d Compare September 17, 2026 08:54
@frederic-hoerni frederic-hoerni changed the title Add reencryption API (MVP) Add reencryption Sep 17, 2026
@tlaurion

Copy link
Copy Markdown

Would be awesome if this go cryptsetup api was in its own repository. u-root and other projects would benefit of this as well

@frederic-hoerni
frederic-hoerni force-pushed the feature/luks-reencryption branch from 0ba1781 to d6ced40 Compare September 22, 2026 16:25
@frederic-hoerni

Copy link
Copy Markdown
Collaborator Author

Would be awesome if this go cryptsetup api was in its own repository

@tlaurion, we have no plan for that at the moment.

@frederic-hoerni
frederic-hoerni force-pushed the feature/luks-reencryption branch from 81a124a to a95d46c Compare September 23, 2026 08:32
@frederic-hoerni
frederic-hoerni marked this pull request as ready for review September 24, 2026 06:34
@frederic-hoerni frederic-hoerni self-assigned this Sep 24, 2026
Comment thread internal/luksview/tokens_test.go
Comment thread internal/luks2/cryptsetup.go Outdated
Comment thread luks2/backend.go
Comment thread keydata_luks.go Outdated
@frederic-hoerni

Copy link
Copy Markdown
Collaborator Author

Please rebase on top of master (and expect conflicts).

@ruifm ruifm left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great!

Comment thread internal/luks2/cryptsetup.go Outdated
Comment thread luks2/reencrypt.go
Comment thread reencrypt.go
Comment thread luks2/luks2_test.go
Comment thread luks2/reencrypt.go
Comment thread luks2/reencrypt.go
Comment thread luks2/reader.go Outdated
Comment thread internal/luks2/cryptsetup.go
@frederic-hoerni
frederic-hoerni force-pushed the feature/luks-reencryption branch from 5f920db to d95f727 Compare September 30, 2026 07:22

@ruifm ruifm left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@frederic-hoerni
frederic-hoerni force-pushed the feature/luks-reencryption branch from f4029ad to 3176726 Compare September 30, 2026 12:44
@frederic-hoerni
frederic-hoerni force-pushed the feature/luks-reencryption branch from 3176726 to 41dcfa7 Compare September 30, 2026 13:58
This adds support for reencrypting active LUKS2 containers.

Main changes:
- API functions:
    * ReencryptionForActiveVolume
    * Initialize
    * Resume
    * Status
- Named LUKS2 tokens now may have 2 keyslots, which happens during
  reencryption.
OpenRead now sets a shared lock on the LUKS2 container so that reencryption
cannot happen while a StorageContainerReader is open.
@frederic-hoerni
frederic-hoerni force-pushed the feature/luks-reencryption branch from 97850e3 to 6bff423 Compare October 1, 2026 09:47
)

const UsageActivate = `
usage: secboot-tool activate [<options>] <device> <active-name> <unlock-key-hex>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a debugging tool? Is there a plan to ship that?

I do not think we want any key in command line. Unless everything was running in pid namespaces, this is public information.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a debugging tool. We do not plan to ship it for now.

@valentindavid valentindavid left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very quick pass. Just some questions and remark for now.

"--token-replace", "/dev/null"); err == nil {
features |= FeatureTokenReplace
}
if _, err := cryptsetupCmd(nil, "--test-args", "reencrypt", "--keys-from-stdin-sizes", "1,2",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note while reviewing. The feature is https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/958

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose we should wait for this to land upstream before we ship that in snapd.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it does not prevent merging this PR.
If cryptsetup is lacking the feature, then the following code:

reencryption, err := secboot.ReencryptionForActiveVolume(activeName)

will return this error: luks2 backend is missing the reencryption feature.

"--key-file", "-",
"--batch-mode",
"--resume-only",
"--progress-frequency", "1",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it is something we will want to configure in the future.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is a need for a higher frequency. If we later find a need for a lower frequency we will discuss it then.

It is also worth noting that this parameter is somewhat approximate. With a setting of 1 second, the actually observed period between 2 reports is between 1 and 3 seconds.

}

// ReadCryptsetupStatus returns the cryptsetup status of an active dm volume
func ReadCryptsetupStatus(activeName string) (*CryptsetupStatus, error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit afraid about parsing that. it feels like it might change in the future.

Should we add a json output upstream?

@frederic-hoerni frederic-hoerni Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cryptsetup status has a --debug-json option that does not produce any json. Attempts to upstream might be unpredictable.

Besides, don't we have integration tests that would detect incompatible changes?

Comment thread internal/luksview/view.go

// KeyslotNamesSortedById returns the token names sorted
// by their keyslot identifier (in ascending order)
// Assumption: there is only one keyslot by token

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason to sort them? I wonder if we could use something different that is a bit more safe without this assumption.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is based on how cryptsetup is organized. They look at all possible slots and unlock the active ones in this order.

Comment thread internal/luksview/view.go
case "online-reencrypt-v3":
return true
default:
continue

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if it is online-reencrypt-v4? We return false here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok we can accept these as well. v4 is not used at the moment, but I found that cryptsetup has a test that considers online-reencrypt-v999 correct.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread log/log.go Outdated

func Logf(level int, format string, v ...any) {
if level <= logLevelPolicy {
fmt.Fprintf(os.Stderr, format, v...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to be able to initialized the output from snapd.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added SetOuput.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant "SetOutput":

func SetOutput(newOutput io.Writer)` { ... }

Comment thread luks2/keyslot.go
if len(i.keyslotIds) == 1 {
return i.keyslotIds[0]
} else {
return luks2.AnySlot

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this the right return for len(i.keyslotIds) > 1. Do we verify somewhere else that we can have only 0 or 1?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can have 2 keyslots here. In that case, we cannot decide to choose one over the other. So we return Any.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a description with this in mind.

Comment thread luks2/reencrypt.go
streamLines := func(pipe io.Reader, outputDone chan<- struct{}) {
scanner := bufio.NewScanner(pipe)
for scanner.Scan() {
rawBytes := scanner.Bytes() // should be in JSON format

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is newline delimited json sequence? Go's json does not support json sequence for rfc7464?

@frederic-hoerni frederic-hoerni Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a newline delimited json sequence. But not rfc7464 as there is no ASCII Record Separator (0x1E).
Moreover we may also catch non-json errors here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants