feat: add Secure Boot MOK key enrollment and module signing

Generate DKMS MOK key pair during OS install, sign all unsigned kernel
modules, and enroll the MOK certificate using the user's master password.
On reboot, MokManager prompts the user to complete enrollment. Re-enrolls
on every boot if the key exists but isn't enrolled yet. Adds setup wizard
dialog to inform the user about the MokManager prompt.
This commit is contained in:
Aiden McClelland
2026-03-11 15:18:13 -06:00
parent 10a5bc0280
commit effcec7e2e
23 changed files with 400 additions and 20 deletions

View File

@@ -40,6 +40,7 @@ export class AppComponent {
this.stateService.dataDriveGuid = status.guid
}
this.stateService.attach = status.attach
this.stateService.mokEnrolled = status.mokEnrolled
await this.router.navigate(['/language'])
break

View File

@@ -0,0 +1,77 @@
import { Component } from '@angular/core'
import { i18nPipe } from '@start9labs/shared'
import { TuiButton, TuiDialogContext, TuiIcon } from '@taiga-ui/core'
import { injectContext } from '@taiga-ui/polymorpheus'
@Component({
standalone: true,
imports: [TuiButton, TuiIcon, i18nPipe],
template: `
<div class="icon-container">
<tui-icon icon="@tui.shield-check" class="mok-icon" />
</div>
<h3>{{ 'Secure Boot Key Enrollment' | i18n }}</h3>
<p>
{{
'A signing key was enrolled for Secure Boot. On the next reboot, a blue screen (MokManager) will appear.'
| i18n
}}
</p>
<ol>
<li>Select "Enroll MOK"</li>
<li>Select "Continue"</li>
<li>{{ 'Enter your StartOS master password when prompted' | i18n }}</li>
<li>Select "Reboot"</li>
</ol>
<footer>
<button tuiButton (click)="context.completeWith(true)">
{{ 'Got it' | i18n }}
</button>
</footer>
`,
styles: `
:host {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.icon-container {
margin-bottom: 1rem;
}
.mok-icon {
width: 3rem;
height: 3rem;
color: var(--tui-status-info);
}
h3 {
margin: 0 0 0.5rem;
}
p {
margin: 0 0 1rem;
color: var(--tui-text-secondary);
}
ol {
text-align: left;
margin: 0 0 1.5rem;
padding-left: 1.5rem;
li {
margin-bottom: 0.25rem;
}
}
footer {
display: flex;
justify-content: center;
}
`,
})
export class MokEnrollmentDialog {
protected readonly context = injectContext<TuiDialogContext<boolean>>()
}

View File

@@ -390,6 +390,7 @@ export default class DrivesPage {
this.stateService.dataDriveGuid = result.guid
this.stateService.attach = result.attach
this.stateService.mokEnrolled = result.mokEnrolled
loader.unsubscribe()

View File

@@ -19,6 +19,7 @@ import { ApiService } from '../services/api.service'
import { StateService } from '../services/state.service'
import { DocumentationComponent } from '../components/documentation.component'
import { MatrixComponent } from '../components/matrix.component'
import { MokEnrollmentDialog } from '../components/mok-enrollment.dialog'
import { RemoveMediaDialog } from '../components/remove-media.dialog'
import { T } from '@start9labs/start-sdk'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
@@ -275,6 +276,20 @@ export default class SuccessPage implements AfterViewInit {
await this.api.exit()
}
}
if (this.stateService.mokEnrolled && this.result.needsRestart) {
this.dialogs
.openComponent<boolean>(
new PolymorpheusComponent(MokEnrollmentDialog),
{
label: 'Secure Boot',
size: 's',
dismissible: false,
closeable: true,
},
)
.subscribe()
}
} catch (e: any) {
this.errorService.handleError(e)
}

View File

@@ -116,6 +116,7 @@ export class MockApiService extends ApiService {
return {
guid: 'mock-data-guid',
attach: !params.dataDrive.wipe,
mokEnrolled: false,
}
}

View File

@@ -42,6 +42,7 @@ export class StateService {
// From install response or status response (incomplete)
dataDriveGuid = ''
attach = false
mokEnrolled = false
// Set during setup flow
setupType?: SetupType
@@ -116,6 +117,7 @@ export class StateService {
this.keyboard = ''
this.dataDriveGuid = ''
this.attach = false
this.mokEnrolled = false
this.setupType = undefined
this.recoverySource = undefined
}

View File

@@ -13,6 +13,7 @@ export interface InstallOsParams {
export interface InstallOsRes {
guid: string // data drive guid
attach: boolean
mokEnrolled: boolean
}
// === Disk Info Helpers ===

View File

@@ -709,4 +709,8 @@ export default {
786: 'Automatisch',
787: 'Ausgehender Datenverkehr',
788: 'Gateway verwenden',
789: 'Secure-Boot-Schlüsselregistrierung',
790: 'Ein Signaturschlüssel wurde für Secure Boot registriert. Beim nächsten Neustart erscheint ein blauer Bildschirm (MokManager).',
791: 'Geben Sie Ihr StartOS-Master-Passwort ein, wenn Sie dazu aufgefordert werden',
792: 'Verstanden',
} satisfies i18n

View File

@@ -709,4 +709,9 @@ export const ENGLISH: Record<string, number> = {
'Auto': 786,
'Outbound Traffic': 787,
'Use gateway': 788,
// Secure Boot MOK enrollment
'Secure Boot Key Enrollment': 789,
'A signing key was enrolled for Secure Boot. On the next reboot, a blue screen (MokManager) will appear.': 790,
'Enter your StartOS master password when prompted': 791,
'Got it': 792,
}

View File

@@ -709,4 +709,8 @@ export default {
786: 'Automático',
787: 'Tráfico saliente',
788: 'Usar gateway',
789: 'Registro de clave de Secure Boot',
790: 'Se registró una clave de firma para Secure Boot. En el próximo reinicio, aparecerá una pantalla azul (MokManager).',
791: 'Ingrese su contraseña maestra de StartOS cuando se le solicite',
792: 'Entendido',
} satisfies i18n

View File

@@ -709,4 +709,8 @@ export default {
786: 'Automatique',
787: 'Trafic sortant',
788: 'Utiliser la passerelle',
789: "Enregistrement de la clé Secure Boot",
790: "Une clé de signature a été enregistrée pour Secure Boot. Au prochain redémarrage, un écran bleu (MokManager) apparaîtra.",
791: 'Entrez votre mot de passe principal StartOS lorsque vous y êtes invité',
792: 'Compris',
} satisfies i18n

View File

@@ -709,4 +709,8 @@ export default {
786: 'Automatycznie',
787: 'Ruch wychodzący',
788: 'Użyj bramy',
789: 'Rejestracja klucza Secure Boot',
790: 'Klucz podpisu został zarejestrowany dla Secure Boot. Przy następnym uruchomieniu pojawi się niebieski ekran (MokManager).',
791: 'Wprowadź swoje hasło główne StartOS po wyświetleniu monitu',
792: 'Rozumiem',
} satisfies i18n