Skip to content

feat: implement new TPM encryption key flow with PIN support #1082

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 10, 2025
31 changes: 31 additions & 0 deletions apps/ubuntu_bootstrap/integration_test/screenshot_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,37 @@ Future<void> main() async {
variant: themeVariant,
);

testWidgets(
'passphrase type',
(tester) async {
await tester.runApp(
() => runInstallerApp(
[
'--dry-run-config=examples/dry-run-configs/tpm.yaml',
],
theme: currentTheme,
),
);
await tester.pumpAndSettle();

await tester.jumpToStorageWizard();
await tester.pumpAndSettle();

await tester.testStoragePage(
type: StorageType.erase,
);

await tester.testGuidedCapabilityPage(
guidedCapability: GuidedCapability.CORE_BOOT_ENCRYPTED,
);

await tester.testPassphraseTypePage(
screenshot: '$currentThemeName/passphrase-type',
);
},
variant: themeVariant,
);

testWidgets(
'passphrase',
(tester) async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ void main() {
guidedCapability: GuidedCapability.CORE_BOOT_ENCRYPTED,
);

await tester.testPassphrasePage(passphrase: '', skip: true);
await tester.testPassphraseTypePage();

await tester.testIdentityPage(identity: identity, password: 'password');
await expectIdentity(identity);
Expand Down Expand Up @@ -370,6 +370,9 @@ void main() {
guidedCapability: GuidedCapability.CORE_BOOT_ENCRYPTED,
);

await tester.testPassphraseTypePage(
passphraseType: PassphraseType.passphrase,
);
await tester.testPassphrasePage(passphrase: 'passphrase');

await tester.testIdentityPage(identity: identity, password: 'password');
Expand All @@ -391,6 +394,66 @@ void main() {
);
});

testWidgets('tpm with pin', (tester) async {
const identity = Identity(
realname: 'User',
hostname: 'ubuntu',
username: 'user',
);

await tester.runApp(
() => app.main([
'--source-catalog=examples/sources/tpm.yaml',
'--dry-run-config=examples/dry-run-configs/tpm.yaml',
'--',
'--bootloader=uefi',
]),
);

await tester.pumpAndSettle();
await tester.testLocalePage();
await tester.testAccessibilityPage();
await tester.testKeyboardPage();
await tester.testNetworkPage(mode: ConnectMode.none);
await tester.testRefreshPage();
await tester.testAutoinstallPage();
await tester.testSourceSelectionPage();
await tester.testCodecsAndDriversPage();

await tester.testStoragePage(
type: StorageType.erase,
);
await tester.testGuidedCapabilityPage(
guidedCapability: GuidedCapability.CORE_BOOT_ENCRYPTED,
);

await tester.testPassphraseTypePage(
passphraseType: PassphraseType.pin,
);
await tester.testPassphrasePage(
passphrase: '12345',
passphraseType: PassphraseType.pin,
);

await tester.testIdentityPage(identity: identity, password: 'password');
await expectIdentity(identity);

await tester.testTimezonePage();
await tester.testConfirmPage();
await tester.testInstallPage();
await tester.testRecoveryKeyPage();
await tester.testDonePage();

final windowClosed = YaruTestWindow.waitForClosed();
await tester.tapContinueTesting();
await expectLater(windowClosed, completes);

await verifySubiquityConfig(
identity: identity,
capability: GuidedCapability.CORE_BOOT_ENCRYPTED,
);
});

testWidgets('manual partitioning', (tester) async {
final storage = [
fakeDisk(
Expand Down
35 changes: 23 additions & 12 deletions apps/ubuntu_bootstrap/lib/l10n/ubuntu_bootstrap_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -171,21 +171,32 @@
"installCodecsSubtitle": "Including but not limited to MP3, MP4, MOV and similar",
"batteryWarning": "The computer is not plugged in to a power source.",
"offlineWarning": "You are currently offline",
"choosePassphraseTitle": "Disk passphrase",
"choosePassphraseHeader": "Create a passphrase",
"choosePassphraseBody": "You need a passphrase to encrypt your files. You will be prompted for your passphrase every time you turn on your computer.",
"choosePassphraseHint": "Choose a passphrase",
"choosePassphraseConfirmHint": "Confirm the passphrase",
"choosePassphraseRequired": "A passphrase is required",
"choosePassphraseMismatch": "The passphrases do not match",
"choosePassphraseInfoHeader": "Store your passphrase somewhere safe",
"choosePassphraseInfoBody": "If you lose your passphrase, you will lose all your data.",
"chooseOptionalPassphraseHeader": "Create a passphrase (optional)",
"chooseOptionalPassphraseBody": "A passphrase can help protect your data even if your hardware gets compromised. You will need to enter the passphrase every time you turn on your computer. You will not be able to remove it later",
"choosePassphraseBody": "You will need to enter your passphrase every time you turn on your computer. This passphrase is different from your user password.",
"choosePassphraseInfoHeader": "Make sure to save your passphrase",
"choosePassphraseInfoBody": "If you lose it, you will lose all your data.",
"chooseOptionalPassphraseInfoHeader": "Store your passphrase and recovery key somewhere safe",
"chooseOptionalPassphraseInfoBody": "If you lose your passphrase, you will lose all your data. The passphrase does not replace the recovery key or your user password.",
"createPassphrase": "Create a passphrase",
"confirmPassphrase": "Confirm the passphrase",
"passphrasePageTitle": "Encryption",
"passphrasePageHeaderPassphrase": "Set an encryption passphrase",
"passphrasePageHeaderPin": "Set an encryption PIN",
"passphrasePageBodyPassphrase": "You will need to enter your passphrase every time you turn on your computer. This passphrase is different from your user password. You will be able to change it later, but not disable it. If you forget your passphrase, you can regain access to the disk by using the recovery key.",
"passphrasePageBodyPin": "You will need to enter your PIN every time you turn on your computer. This PIN is different from your user password. You will be able to change it later, but not disable it. If you forget your PIN, you can regain access to the disk by using the recovery key.",
"passphrasePageChoosePassphraseHint": "Passphrase",
"passphrasePageConfirmPassphraseHint": "Confirm passphrase",
"passphrasePageRequiredPassphrase": "A passphrase is required",
"passphrasePageMismatchPassphrase": "The passphrases do not match",
"passphrasePageChoosePinHint": "PIN",
"passphrasePageConfirmPinHint": "Confirm PIN",
"passphrasePageRequiredPin": "A PIN is required",
"passphrasePageMismatchPin": "The PINs do not match",
"passphraseTypePassphraseTileTitle": "Require a passphrase",
"passphraseTypePassphraseTileSubTitle": "Most secure. You will need to enter a longer passphrase every time you turn on your computer.",
"passphraseTypePinTileTitle": "Require a PIN",
"passphraseTypePinTileSubTitle": "More secure. You will need to enter a numeric PIN every time you turn on your computer.",
"passphraseTypeNoneTileTitle": "Unlock disk automatically",
"passphraseTypePageHeader": "Encryption PIN or passphrase",
"passphraseTypePageBody": "By default, the computer’s Trusted Platform Module (TPM) will unlock the disk during startup. However, you can also require a PIN or a passphrase to further protect your data.",
"installationTypeTitle": "Disk setup",
"installationTypeHeader": "How do you want to install {DISTRO}?",
"@installationTypeHeader": {
Expand Down
148 changes: 107 additions & 41 deletions apps/ubuntu_bootstrap/lib/l10n/ubuntu_bootstrap_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -719,12 +719,6 @@ abstract class UbuntuBootstrapLocalizations {
/// **'You are currently offline'**
String get offlineWarning;

/// No description provided for @choosePassphraseTitle.
///
/// In en, this message translates to:
/// **'Disk passphrase'**
String get choosePassphraseTitle;

/// No description provided for @choosePassphraseHeader.
///
/// In en, this message translates to:
Expand All @@ -734,80 +728,152 @@ abstract class UbuntuBootstrapLocalizations {
/// No description provided for @choosePassphraseBody.
///
/// In en, this message translates to:
/// **'You need a passphrase to encrypt your files. You will be prompted for your passphrase every time you turn on your computer.'**
/// **'You will need to enter your passphrase every time you turn on your computer. This passphrase is different from your user password.'**
String get choosePassphraseBody;

/// No description provided for @choosePassphraseHint.
/// No description provided for @choosePassphraseInfoHeader.
///
/// In en, this message translates to:
/// **'Make sure to save your passphrase'**
String get choosePassphraseInfoHeader;

/// No description provided for @choosePassphraseInfoBody.
///
/// In en, this message translates to:
/// **'If you lose it, you will lose all your data.'**
String get choosePassphraseInfoBody;

/// No description provided for @chooseOptionalPassphraseInfoHeader.
///
/// In en, this message translates to:
/// **'Store your passphrase and recovery key somewhere safe'**
String get chooseOptionalPassphraseInfoHeader;

/// No description provided for @chooseOptionalPassphraseInfoBody.
///
/// In en, this message translates to:
/// **'If you lose your passphrase, you will lose all your data. The passphrase does not replace the recovery key or your user password.'**
String get chooseOptionalPassphraseInfoBody;

/// No description provided for @passphrasePageTitle.
///
/// In en, this message translates to:
/// **'Encryption'**
String get passphrasePageTitle;

/// No description provided for @passphrasePageHeaderPassphrase.
///
/// In en, this message translates to:
/// **'Set an encryption passphrase'**
String get passphrasePageHeaderPassphrase;

/// No description provided for @passphrasePageHeaderPin.
///
/// In en, this message translates to:
/// **'Set an encryption PIN'**
String get passphrasePageHeaderPin;

/// No description provided for @passphrasePageBodyPassphrase.
///
/// In en, this message translates to:
/// **'Choose a passphrase'**
String get choosePassphraseHint;
/// **'You will need to enter your passphrase every time you turn on your computer. This passphrase is different from your user password. You will be able to change it later, but not disable it. If you forget your passphrase, you can regain access to the disk by using the recovery key.'**
String get passphrasePageBodyPassphrase;

/// No description provided for @choosePassphraseConfirmHint.
/// No description provided for @passphrasePageBodyPin.
///
/// In en, this message translates to:
/// **'Confirm the passphrase'**
String get choosePassphraseConfirmHint;
/// **'You will need to enter your PIN every time you turn on your computer. This PIN is different from your user password. You will be able to change it later, but not disable it. If you forget your PIN, you can regain access to the disk by using the recovery key.'**
String get passphrasePageBodyPin;

/// No description provided for @choosePassphraseRequired.
/// No description provided for @passphrasePageChoosePassphraseHint.
///
/// In en, this message translates to:
/// **'Passphrase'**
String get passphrasePageChoosePassphraseHint;

/// No description provided for @passphrasePageConfirmPassphraseHint.
///
/// In en, this message translates to:
/// **'Confirm passphrase'**
String get passphrasePageConfirmPassphraseHint;

/// No description provided for @passphrasePageRequiredPassphrase.
///
/// In en, this message translates to:
/// **'A passphrase is required'**
String get choosePassphraseRequired;
String get passphrasePageRequiredPassphrase;

/// No description provided for @choosePassphraseMismatch.
/// No description provided for @passphrasePageMismatchPassphrase.
///
/// In en, this message translates to:
/// **'The passphrases do not match'**
String get choosePassphraseMismatch;
String get passphrasePageMismatchPassphrase;

/// No description provided for @choosePassphraseInfoHeader.
/// No description provided for @passphrasePageChoosePinHint.
///
/// In en, this message translates to:
/// **'Store your passphrase somewhere safe'**
String get choosePassphraseInfoHeader;
/// **'PIN'**
String get passphrasePageChoosePinHint;

/// No description provided for @choosePassphraseInfoBody.
/// No description provided for @passphrasePageConfirmPinHint.
///
/// In en, this message translates to:
/// **'If you lose your passphrase, you will lose all your data.'**
String get choosePassphraseInfoBody;
/// **'Confirm PIN'**
String get passphrasePageConfirmPinHint;

/// No description provided for @chooseOptionalPassphraseHeader.
/// No description provided for @passphrasePageRequiredPin.
///
/// In en, this message translates to:
/// **'Create a passphrase (optional)'**
String get chooseOptionalPassphraseHeader;
/// **'A PIN is required'**
String get passphrasePageRequiredPin;

/// No description provided for @chooseOptionalPassphraseBody.
/// No description provided for @passphrasePageMismatchPin.
///
/// In en, this message translates to:
/// **'A passphrase can help protect your data even if your hardware gets compromised. You will need to enter the passphrase every time you turn on your computer. You will not be able to remove it later'**
String get chooseOptionalPassphraseBody;
/// **'The PINs do not match'**
String get passphrasePageMismatchPin;

/// No description provided for @chooseOptionalPassphraseInfoHeader.
/// No description provided for @passphraseTypePassphraseTileTitle.
///
/// In en, this message translates to:
/// **'Store your passphrase and recovery key somewhere safe'**
String get chooseOptionalPassphraseInfoHeader;
/// **'Require a passphrase'**
String get passphraseTypePassphraseTileTitle;

/// No description provided for @chooseOptionalPassphraseInfoBody.
/// No description provided for @passphraseTypePassphraseTileSubTitle.
///
/// In en, this message translates to:
/// **'If you lose your passphrase, you will lose all your data. The passphrase does not replace the recovery key or your user password.'**
String get chooseOptionalPassphraseInfoBody;
/// **'Most secure. You will need to enter a longer passphrase every time you turn on your computer.'**
String get passphraseTypePassphraseTileSubTitle;

/// No description provided for @createPassphrase.
/// No description provided for @passphraseTypePinTileTitle.
///
/// In en, this message translates to:
/// **'Create a passphrase'**
String get createPassphrase;
/// **'Require a PIN'**
String get passphraseTypePinTileTitle;

/// No description provided for @passphraseTypePinTileSubTitle.
///
/// In en, this message translates to:
/// **'More secure. You will need to enter a numeric PIN every time you turn on your computer.'**
String get passphraseTypePinTileSubTitle;

/// No description provided for @passphraseTypeNoneTileTitle.
///
/// In en, this message translates to:
/// **'Unlock disk automatically'**
String get passphraseTypeNoneTileTitle;

/// No description provided for @passphraseTypePageHeader.
///
/// In en, this message translates to:
/// **'Encryption PIN or passphrase'**
String get passphraseTypePageHeader;

/// No description provided for @confirmPassphrase.
/// No description provided for @passphraseTypePageBody.
///
/// In en, this message translates to:
/// **'Confirm the passphrase'**
String get confirmPassphrase;
/// **'By default, the computer’s Trusted Platform Module (TPM) will unlock the disk during startup. However, you can also require a PIN or a passphrase to further protect your data.'**
String get passphraseTypePageBody;

/// No description provided for @installationTypeTitle.
///
Expand Down
Loading
Loading