1
1
mirror of https://github.com/NixOS/mobile-nixos.git synced 2024-12-17 04:51:31 +03:00

Merge pull request #234 from samueldr-wip/feature/stage-1-passphrase

stage-1: Add interactive LUKS decrypting
This commit is contained in:
Samuel Dionne-Riel 2020-11-07 20:10:21 -05:00 committed by GitHub
commit 63d49f51ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 666 additions and 74 deletions

View File

@ -75,6 +75,10 @@ module Mounting
[mount_point, task] [mount_point, task]
end.to_h end.to_h
auto_depend_mount_points(mount_points) auto_depend_mount_points(mount_points)
(Configuration["luksDevices"] or []).each do |mapper, info|
Tasks::Luks.new(info["device"], mapper)
end
end end
end end

View File

@ -1,44 +1,120 @@
# Progress-reporting plumbing # Progress-reporting plumbing
module Progress module Progress
SOCKET = "/run/mobile-nixos-init.socket" SOCKET_PREFIX = "/run/mobile-nixos-init"
# Starts the queue sockets.
# This is waiting for /run/ to be available.
# It needs to be possible to let some consumers (e.g. splash) alive from
# stage-1 and waiting for fresh messages from stage-2.
# A stage-2 process could ask that splash to "hand-off" to a stage-2 splash.
def self.start() def self.start()
@progress = 0 @progress = 0
$logger.debug("Starting progress IPC through ZeroMQ") $logger.debug("Starting progress IPC through ZeroMQ")
$logger.debug(" -> #{SOCKET}")
@pub = ZMQ::Pub.new("ipc://#{SOCKET}") $logger.debug(" -> messages: #{SOCKET_PREFIX}")
@messages_socket = ZMQ::Pub.new("ipc://#{SOCKET_PREFIX}-messages")
$logger.debug(" -> replies: #{SOCKET_PREFIX}")
@replies_socket = ZMQ::Sub.new("ipc://#{SOCKET_PREFIX}-replies", "")
end end
# Prefer not sending messages directly, rather use the helpers. # Given values (in a Hash), it will update the state with them, and send the
def self.publish(msg) # updated state to the messages queue.
msg = msg.to_json # +nil+ values are compacted out of the state.
if @pub def self.update(values)
@state ||= {}
@state.merge!(values).compact!
send_state()
end
# Get a specific value from the state.
# This should be done as little as possible.
def self.get(attr)
@state ||= {}
@state[attr]
end
# See +#get+
def self.[](name)
get(name)
end
# Send the current state over the messages socket.
def self.send_state()
msg = @state.to_json
if @messages_socket
$logger.debug("[send] #{msg}") $logger.debug("[send] #{msg}")
@pub.send(msg) @messages_socket.send(msg)
else else
$logger.debug("[send] Couldn't send #{msg}") $logger.debug("[send] Socket not open yet.")
$logger.debug("[send] Couldn't send: #{msg}")
end end
end end
# Sets the progress to a specific amount
def self.set(amount)
@progress = amount
publish({
progress: @progress,
})
end
# Executes the given block, showing the message beforehand, and removing the # Executes the given block, showing the message beforehand, and removing the
# message once done. # message once done.
def self.with_message(msg) def self.exec_with_message(label)
publish({ previous = get(:label)
progress: @progress, update({label: label})
label: msg, ret = yield
}) update({label: previous})
yield ret
publish({ end
progress: @progress,
def self.ask(placeholder, label: nil)
identifier = "0x#{Random.rand(0xFFFFF).to_s(16)}"
previous_label = get(:label)
Progress.update({label: label}) if label
update(command: {
name: "ask",
identifier: identifier,
placeholder: placeholder,
}) })
value = loop do
# Keep progress state updated for processes attaching late.
send_state()
value =
each_replies do |reply|
# A reply for the current question?
if reply and reply["type"] == "reply" and reply["identifier"] == identifier
break reply["value"]
else
nil
end
end
break value if value
# Leave some breathing room to the CPU!
sleep(0.1)
end
update({label: previous_label})
value
end
# Read one reply
# If none are available, returns nil
def self.read_reply()
begin
msg = @replies_socket.recv(LibZMQ::DONTWAIT).to_str
$logger.debug("[recv] #{msg}")
JSON.parse(msg)
rescue Errno::EWOULDBLOCK
# No message?
nil
end
end
# Reads replies until there are none
def self.each_replies()
loop do
msg = read_reply
break unless msg
yield msg
end
end end
end end

View File

@ -51,6 +51,30 @@ module System
Kernel.spawn(*args) Kernel.spawn(*args)
end end
# Runs a long-running task in the background while we keep the progress
# reporting active.
def self.run_long_running(*args)
pretty_command = prettify_command(*args)
pid = System.spawn(*args)
ret = nil
loop do
# Update progress
Progress.send_state()
# Look at the status
break if ret = Process.wait(pid, Process::WNOHANG)
# Don't loop too tightly
sleep(0.1)
end
status = $?.exitstatus
if status == 127
raise CommandNotFound.new("Command not found... `#{pretty_command}` (#{status})")
elsif !$?.success?
raise CommandError.new("Command failed... `#{pretty_command}` (#{status})")
end
end
# Discovers the location of given program name. # Discovers the location of given program name.
def self.which(program_name) def self.which(program_name)
ENV["PATH"].split(":").each do |path| ENV["PATH"].split(":").each do |path|

View File

@ -43,7 +43,7 @@ module Tasks
# Update the current progress # Update the current progress
count = @tasks.length.to_f count = @tasks.length.to_f
Progress.set((100 * (1 - (todo.length / count))).ceil) Progress.update({progress: (100 * (1 - (todo.length / count))).ceil})
todo.each do |task| todo.each do |task|
if task._try_run_task then if task._try_run_task then

View File

@ -11,7 +11,7 @@ class Tasks::AutoResize < Task
def run() def run()
log("Resizing #{@device}...") log("Resizing #{@device}...")
if @type.match(/^ext[234]$/) if @type.match(/^ext[234]$/)
Progress.with_message("Verifying #{@device}...") do Progress.exec_with_message("Verifying #{@device}...") do
# TODO: Understand the actual underlying issue with e2fsck. # TODO: Understand the actual underlying issue with e2fsck.
# It seems `e2fsck` succeeds, according to the output, but has a >0 exit # It seems `e2fsck` succeeds, according to the output, but has a >0 exit
# status. Running it again in those situations is a no-op, which is weird # status. Running it again in those situations is a no-op, which is weird
@ -19,14 +19,14 @@ class Tasks::AutoResize < Task
# This is why we unconditionally run it once, then twice. # This is why we unconditionally run it once, then twice.
# The second will hopefully abort the boot if it fails too. # The second will hopefully abort the boot if it fails too.
begin begin
System.run("e2fsck", "-fp", @device) System.run_long_running("e2fsck", "-fp", @device)
rescue System::CommandError rescue System::CommandError
$logger.info("Re-running e2fsc...") $logger.info("Re-running e2fsc...")
System.run("e2fsck", "-fp", @device) System.run_long_running("e2fsck", "-fp", @device)
end end
end end
Progress.with_message("Resizing #{@device}...") do Progress.exec_with_message("Resizing #{@device}...") do
System.run("resize2fs", "-f", @device) System.run_long_running("resize2fs", "-f", @device)
end end
else else
$logger.warn("Cannot resize #{@type}... filesystem left untouched.") $logger.warn("Cannot resize #{@type}... filesystem left untouched.")

64
boot/init/tasks/luks.rb Normal file
View File

@ -0,0 +1,64 @@
# Opens LUKS devices
class Tasks::Luks < Task
attr_reader :source
attr_reader :mapper
TRIES = 10
class ExistingLuksTask < StandardError
end
class CouldNotUnlock < StandardError
end
def self.register(mapper, instance)
@registry ||= {}
unless @registry[mapper].nil? then
raise ExistingLuksTask.new("LUKS task for '#{mapper}' already exists.")
end
@registry[mapper] = instance
end
def self.registry()
@registry
end
def initialize(source, mapper)
@source = source
@mapper = mapper
add_dependency(:Task, Tasks::UDev.instance)
add_dependency(:Files, source)
add_dependency(:Mount, "/run")
add_dependency(:Target, :Environment)
self.class.register(@mapper, self)
end
def run()
FileUtils.mkdir_p("/run/cryptsetup")
TRIES.times do
passphrase = Progress.ask("Passphrase for #{mapper}")
begin
Progress.exec_with_message("Checking...") do
# TODO: implement with process redirection rather than shelling out
System.run("echo #{passphrase.shellescape} | exec cryptsetup luksOpen #{source.shellescape} #{mapper.shellescape}")
end
Progress.update({label: nil})
# If we're there, we're done!
return
rescue System::CommandError
Progress.update({label: "Wrong passphrase given..."})
end
end
# We failed multiple times.
raise CouldNotUnlock.new("Could not unlock #{source}; tried #{TRIES} times.")
end
def name()
"#{super}(#{source}, #{mapper})"
end
end

View File

@ -24,14 +24,17 @@ class Tasks::Splash < SingletonTask
# Implementation details-y; ask for the splash applet to be exited. # Implementation details-y; ask for the splash applet to be exited.
def quit(reason) def quit(reason)
# Ensures the progress is shown
Progress.update({progress: 100, label: reason})
# Command it to quit
Progress.update({command: {name: "quit"}})
# Ensures that if for any reason the splash didn't start in time for the # Ensures that if for any reason the splash didn't start in time for the
# socket to listen to this message, that we'll be quitting it. # socket to listen to this message, that we'll be quitting it.
loop do loop do
# Ensures the progress is shown # Repeatedly send the current state (which has the quit command).
Progress.publish({progress: 100, label: reason}) Progress.send_state()
# Command it to quit
Progress.publish("quit")
# If it has quit, break out! # If it has quit, break out!
break if Process.wait(@pid, Process::WNOHANG) break if Process.wait(@pid, Process::WNOHANG)

View File

@ -0,0 +1,61 @@
# Wraps a raw +lv_keyboard+ in minimal helpers
# This is intended to be used as a singleton instance, where you can
# "re-parent" the keyboard as needed.
class Keyboard < LVGUI::Widget
include Singleton
private
def initialize()
@shown = false
# Attach the keyboard to the current active screen, by default.
super(LVGL::LVKeyboard.new(LVGL::LVDisplay.get_scr_act()))
set_cursor_manage(true)
get_style(LVGL::KB_STYLE::BG).dup.tap do |style|
set_style(LVGL::KB_STYLE::BG, style)
padding = 4
style.body_padding_top = padding
style.body_padding_left = padding
style.body_padding_right = padding
style.body_padding_bottom = padding
style.body_padding_inner = padding
end
set_y(get_parent.get_height())
end
public
def set_height(value)
super(value)
_set_position()
end
def show()
_animate_y(get_parent.get_height() - get_height())
end
def hide()
_animate_y(get_parent.get_height)
end
def _set_position()
if @shown
_animate_y(get_parent.get_height() - get_height())
else
_animate_y(get_parent.get_height())
end
end
def _animate_y(ending)
LVGL::LVAnim.new().tap do |anim|
anim.set_exec_cb(self, :lv_obj_set_y)
anim.set_time(300, 0)
anim.set_values(get_y(), ending)
anim.set_path_cb(LVGL::LVAnim::Path::EASE_OUT)
# Launch the animation
anim.create()
end
end
end

View File

@ -60,6 +60,7 @@ class ProgressBar < LVGUI::Widget
end end
def progress=(val) def progress=(val)
val = 100 if val > 100
@changed = true @changed = true
@progress_amount = val @progress_amount = val
refresh_progress() refresh_progress()

View File

@ -0,0 +1,90 @@
# Wraps a raw +lv_ta+ in minimal helpers
class TextArea < LVGUI::Widget
attr_reader :hidden
def initialize(parent)
super(LVGL::LVTextArea.new(parent))
@hidden = false
set_text("")
set_placeholder_text("")
set_pwd_mode(true)
set_one_line(true)
set_opa_scale_enable(true)
get_style(LVGL::TA_STYLE::BG).dup.tap do |style|
set_style(LVGL::TA_STYLE::BG, style)
style.body_main_color = 0xFF000000
style.body_grad_color = 0xFF000000
style.body_radius = 5
style.body_border_color = 0xFFFFFFFF
style.body_border_width = 3
style.body_border_opa = 255
style.text_color = 0xFFFFFFFF
end
get_style(LVGL::TA_STYLE::PLACEHOLDER).dup.tap do |style|
set_style(LVGL::TA_STYLE::PLACEHOLDER, style)
style.text_color = 0xFFAAAAAA
end
set_cursor_type(get_cursor_type() | LVGL::CURSOR::HIDDEN)
self.event_handler = -> (event) do
return if hidden()
case event
when LVGL::EVENT::CLICKED
Keyboard.instance.set_ta(self)
Keyboard.instance.show()
when LVGL::EVENT::INSERT
# Not exactly right, but right enough.
char = LVGL::FFI.lv_event_get_data().to_str(1)
# Assume there is only one input.
# Also assume Enter sends; that it is a single line.
if char == "\n"
Keyboard.instance.set_ta(nil)
Keyboard.instance.hide()
# Create a new string
# get_text() gives us a Fiddle::Pointer (leaky abstraction!!!)
value = "#{get_text()}"
@on_submit.call(value) if @on_submit
hide()
end
#else
# puts "Unhandled event for #{self}: #{LVGL::EVENT.from_value(event)}"
end
end
end
def show()
@hidden = false
LVGL::LVAnim.new().tap do |anim|
anim.set_exec_cb(self, :lv_obj_set_opa_scale)
anim.set_time(FADE_LENGTH, 0)
anim.set_values(0, 255)
anim.set_path_cb(LVGL::LVAnim::Path::EASE_OUT)
# Launch the animation
anim.create()
end
end
def hide(skip_animation: false)
@hidden = true
if skip_animation
set_opa_scale(0)
return
end
LVGL::LVAnim.new().tap do |anim|
anim.set_exec_cb(self, :lv_obj_set_opa_scale)
anim.set_time(FADE_LENGTH, 0)
anim.set_values(255, 0)
anim.set_path_cb(LVGL::LVAnim::Path::EASE_IN)
# Launch the animation
anim.create()
end
end
def on_submit=(cb)
@on_submit = cb
end
end

View File

@ -13,22 +13,28 @@ end
class UI class UI
attr_reader :screen attr_reader :screen
attr_reader :progress_bar attr_reader :progress_bar
attr_reader :ask_identifier
# As this is not using BaseWindow, LVGUI::init isn't handled for us. # As this is not using BaseWindow, LVGUI::init isn't handled for us.
LVGUI.init() LVGUI.init()
def initialize() def initialize()
add_screen add_screen
add_page
# Biggest of horizontal or vertical; a percent. # Biggest of horizontal or vertical; a percent.
@unit = ([@screen.get_width, @screen.get_height].max * 0.01).ceil @unit = ([@screen.get_width, @screen.get_height].max * 0.01).ceil
add_logo add_logo
add_progress_bar add_progress_bar
add_label add_label
add_textarea
add_keyboard
add_cover # last add_cover # last
end end
def add_label() def add_label()
@label = LVGL::LVLabel.new(@screen) @label = LVGL::LVLabel.new(@page)
@label.get_style(LVGL::LABEL_STYLE::MAIN).dup.tap do |style| @label.get_style(LVGL::LABEL_STYLE::MAIN).dup.tap do |style|
@label.set_style(LVGL::LABEL_STYLE::MAIN, style) @label.set_style(LVGL::LABEL_STYLE::MAIN, style)
style.text_color = 0xFFFFFFFF style.text_color = 0xFFFFFFFF
@ -36,7 +42,7 @@ class UI
@label.set_long_mode(LVGL::LABEL_LONG::BREAK) @label.set_long_mode(LVGL::LABEL_LONG::BREAK)
@label.set_align(LVGL::LABEL_ALIGN::CENTER) @label.set_align(LVGL::LABEL_ALIGN::CENTER)
@label.set_width(@screen.get_width * 0.9) @label.set_width(@page.get_width * 0.9)
@label.set_pos(*center(@label, 0, 5*@unit)) @label.set_pos(*center(@label, 0, 5*@unit))
@label.set_text("") @label.set_text("")
end end
@ -48,15 +54,15 @@ class UI
file = "./logo.svg" if File.exist?("./logo.svg") file = "./logo.svg" if File.exist?("./logo.svg")
return unless file return unless file
if @screen.get_height > @screen.get_width if @page.get_height > @page.get_width
# 80% of the width # 80% of the width
LVGL::Hacks::LVNanoSVG.resize_next_width((@screen.get_width * 0.8).to_i) LVGL::Hacks::LVNanoSVG.resize_next_width((@page.get_width * 0.8).to_i)
else else
# 15% of the height # 15% of the height
LVGL::Hacks::LVNanoSVG.resize_next_height((@screen.get_height * 0.15).to_i) LVGL::Hacks::LVNanoSVG.resize_next_height((@page.get_height * 0.15).to_i)
end end
@logo = LVGL::LVImage.new(@screen) @logo = LVGL::LVImage.new(@page)
@logo.set_src(file) @logo.set_src(file)
# Position the logo # Position the logo
@ -64,9 +70,9 @@ class UI
end end
def add_progress_bar() def add_progress_bar()
@progress_bar = ProgressBar.new(@screen) @progress_bar = ProgressBar.new(@page)
@progress_bar.set_height(3 * @unit) @progress_bar.set_height(3 * @unit)
@progress_bar.set_width(@screen.get_width * 0.7) @progress_bar.set_width(@page.get_width * 0.7)
@progress_bar.set_pos(*center(@progress_bar)) @progress_bar.set_pos(*center(@progress_bar))
end end
@ -81,14 +87,27 @@ class UI
end end
end end
def add_page()
@page = LVGL::LVContainer.new(@screen)
@page.set_width(@screen.get_width)
@page.set_height(@screen.get_height)
@page.get_style(LVGL::CONT_STYLE::MAIN).dup.tap do |style|
@page.set_style(LVGL::CONT_STYLE::MAIN, style)
style.body_main_color = 0xFF000000
style.body_grad_color = 0xFF000000
style.body_border_width = 0
end
end
# Used to handle fade-in/fade-out # Used to handle fade-in/fade-out
# This is because opacity handles multiple overlaid objects wrong. # This is because opacity handles multiple overlaid objects wrong.
def add_cover() def add_cover()
@cover = LVGL::LVObject.new(@screen) @cover = LVGL::LVObject.new(@screen)
# Make it so we can use the opacity to fade in/out # Make it so we can use the opacity to fade in/out
@cover.set_opa_scale_enable(1) @cover.set_opa_scale_enable(true)
@cover.set_width(@screen.get_width()) @cover.set_width(@screen.get_width())
@cover.set_height(@screen.get_height()) @cover.set_height(@screen.get_height())
@cover.set_click(false)
@cover.get_style().dup.tap do |style| @cover.get_style().dup.tap do |style|
@cover.set_style(style) @cover.set_style(style)
@ -101,6 +120,22 @@ class UI
end end
end end
def add_textarea()
@ta = TextArea.new(@page)
@ta.set_width(@page.get_width * 0.9)
@ta.set_pos(*center(@ta, 0, @unit * 14))
# Always present, but initially hidden
@ta.hide(skip_animation: true)
end
def add_keyboard()
@keyboard = Keyboard.instance()
# The keyboard is not added to the page; the page holds the elements that
# may move to ensure they're not covered by the keyboard.
@keyboard.set_parent(@screen)
@keyboard.set_height(@screen.get_width * 0.55)
end
def set_progress(amount) def set_progress(amount)
progress_bar.progress = amount progress_bar.progress = amount
end end
@ -109,6 +144,39 @@ class UI
@label.set_text(text) @label.set_text(text)
end end
# +cb+ is a proc that wille be +#call+'d with the text once submitted.
def ask_user(placeholder: "", identifier: , cb:)
return if identifier == @ask_identifier
@ask_identifier = identifier
@ta.set_placeholder_text(placeholder)
@ta.show()
@keyboard.set_ta(@ta)
@keyboard.show()
bottom_space = @screen.get_height() - (@ta.get_y() + @ta.get_height())
delta = bottom_space - @keyboard.get_height() - 3*@unit
offset_page(delta) if delta < 0
@ta.on_submit = ->(value) do
@ta.set_text("")
offset_page(0)
cb.call(value)
end
end
def offset_page(delta)
LVGL::LVAnim.new().tap do |anim|
anim.set_exec_cb(@page, :lv_obj_set_y)
anim.set_time(300, 0)
anim.set_values(@page.get_y(), delta)
anim.set_path_cb(LVGL::LVAnim::Path::EASE_OUT)
# Launch the animation
anim.create()
end
end
# Fade-in animation # Fade-in animation
# Note that this looks like inverted logic because it is! # Note that this looks like inverted logic because it is!
# We're actually fading-out the cover! # We're actually fading-out the cover!

View File

@ -9,14 +9,17 @@ FADE_LENGTH = 400
PROGRESS_UPDATE_LENGTH = 500 PROGRESS_UPDATE_LENGTH = 500
VERBOSE = !!Args.get(:verbose, false) VERBOSE = !!Args.get(:verbose, false)
SOCKET = File.expand_path(Args.get(:socket, "/run/mobile-nixos-init.socket")) SOCKET = File.expand_path(Args.get(:socket, "/run/mobile-nixos-init"))
# Create the UI # Create the UI
ui = UI.new ui = UI.new
# Socket for status updates # Socket for status updates
puts "[splash] Listening on: ipc://#{SOCKET}" puts "[splash] Listening on: ipc://#{SOCKET}-messages"
$sub = ZMQ::Sub.new("ipc://#{SOCKET}", "") $messages = ZMQ::Sub.new("ipc://#{SOCKET}-messages", "")
puts "[splash] Replying on: ipc://#{SOCKET}-replies"
$replies = ZMQ::Pub.new("ipc://#{SOCKET}-replies")
# Initial fade-in # Initial fade-in
ui.fade_in() ui.fade_in()
@ -28,7 +31,7 @@ LVGUI.main_loop do
# Empty all messages from the queue before continuing. # Empty all messages from the queue before continuing.
loop do loop do
begin begin
msg = JSON.parse($sub.recv(LibZMQ::DONTWAIT).to_str) msg = JSON.parse($messages.recv(LibZMQ::DONTWAIT).to_str)
rescue Errno::EWOULDBLOCK rescue Errno::EWOULDBLOCK
# No messages left? break out! # No messages left? break out!
break break
@ -39,14 +42,33 @@ LVGUI.main_loop do
p msg p msg
end end
# We might have a special command, if we got a String rather than a Hash. # We might have a special command; handle it.
if msg.is_a? String then if msg["command"] then
if msg == "quit" command = msg["command"]
case command["name"]
when "quit"
ui.quit! ui.quit!
else when "ask"
$stderr.puts "[splash] Unexpected command #{msg}..." ui.ask_user(placeholder: command["placeholder"], identifier: command["identifier"], cb: ->(value) do
msg = {
type: "reply",
identifier: command["identifier"],
value: value,
}.to_json
if VERBOSE
print "[splash:send] "
p msg
end end
$replies.send(msg)
end)
else else
$stderr.puts "[splash] Unexpected command #{command.to_json}..."
end
end
# Update the UI... # Update the UI...
# First updating the current progress # First updating the current progress
@ -59,7 +81,6 @@ LVGUI.main_loop do
ui.set_label("") ui.set_label("")
end end
end end
end
end end
$stderr.puts "[splash] Broke out of the rendering loop. That's not supposed to happen." $stderr.puts "[splash] Broke out of the rendering loop. That's not supposed to happen."

View File

@ -0,0 +1,13 @@
Testing examples
================
These examples are made specifically to test features.
They are not to be used as a normal system, as they likely have some fatal
flaws, either usability-wise (e.g. not usable at all) or security-wise (e.g.
default passwords).
More details are available inside each testing systems directories.
Though, please refer to those systems as reference implementation for
validating changes around their features under test!

View File

@ -0,0 +1,45 @@
`qemu-cryptsetup`
=================
What does this test?
--------------------
Using the `hello` system, pre-configured to use the `qemu` system type.
This tests:
- Encryption passphrase at boot
**This test is manual.**
The passphrase in use is:
```
1234
```
Why is this scary?
------------------
- Secrets in the store!
- Well-known insecure passphrase
How is success defined?
-----------------------
The `hello` system applet is booted-to after the user supplies the encryption
passphrase during boot.
How is success defined?
-----------------------
Assuming you are `cd`'d into the root of a Mobile NixOS checkout:
```
nix-build ./examples/testing/qemu-cryptsetup && ./result
```
As always, be mindful of your `NIX_PATH`.

View File

@ -0,0 +1,68 @@
{ config, lib, pkgs, ... }:
let
# This is not a secure or safe way to create an encrypted drive in a build.
# This is SOLELY for testing purposes.
passphrase = "1234";
uuid = "12345678-1234-1234-1234-123456789abc"; # heh
# We are re-using the raw filesystem from the hello system.
rootfsExt4 = (
import ../../hello { device = config.mobile.device.name; }
).build.rootfs;
# This is not a facility from the disk images builder because **it is really
# insecure to use**.
# So, for now, we have an implementation details-y way of producing an
# encrypted rootfs.
encryptedRootfs = pkgs.vmTools.runInLinuxVM (
pkgs.runCommand "encrypted-rootfs" {
buildInputs = [ pkgs.cryptsetup ];
} ''
(PS4=" $ "; set -x
mkdir -p /run/cryptsetup
mkdir -p $out
cd $out
slack=32 # MiB
# Some slack space we'll append to the raw fs
# Used by `--reduce-device-size` read cryptsetup(8).
dd if=/dev/zero of=tmp.img bs=1024 count=$((slack*1024))
# Catting both to ensure it's writable, and to add some slack space at
# the end
cat ${rootfsExt4}/${rootfsExt4.label}.img tmp.img > encrypted.img
rm tmp.img
echo ${builtins.toJSON passphrase} | cryptsetup \
reencrypt \
--encrypt ./encrypted.img \
--reduce-device-size $((slack*1024*1024))
#echo YES |
cryptsetup luksUUID --uuid=${builtins.toJSON uuid} ./encrypted.img
)
''
);
in
{
boot.initrd.luks.devices = {
LUKS-MOBILE-ROOTFS = {
device = "/dev/disk/by-uuid/${uuid}";
};
};
fileSystems = {
"/" = {
device = "/dev/mapper/LUKS-MOBILE-ROOTFS";
fsType = "ext4";
};
};
# Instead of the (mkDefault) rootfs, provide our raw encrypted rootfs.
mobile.generatedFilesystems.rootfs = {
raw = encryptedRootfs;
};
}

View File

@ -0,0 +1,11 @@
let
device = "qemu-x86_64";
system-build = import ../../../. {
inherit device;
configuration = [ { imports = [
../../hello/configuration.nix
./configuration.nix
]; } ];
};
in
system-build.build.default

View File

@ -62,6 +62,14 @@ let
limitations in CI situations. limitations in CI situations.
''; '';
}; };
raw = lib.mkOption {
internal = true;
type = types.nullOr types.package;
default = null;
description = ''
Use an output directly rather than creating it from the options.
'';
};
}; };
config = { config = {
}; };
@ -79,7 +87,8 @@ in
}; };
config = { config = {
system.build.generatedFilesystems = lib.attrsets.mapAttrs (name: {type, id, label, ...} @ attrs: system.build.generatedFilesystems = lib.attrsets.mapAttrs (name: {raw, type, id, label, ...} @ attrs:
if raw != null then raw else
filesystemFunctions."${type}" (attrs // { filesystemFunctions."${type}" (attrs // {
name = label; name = label;
partitionID = id; partitionID = id;

27
modules/luks.nix Normal file
View File

@ -0,0 +1,27 @@
{ config, lib, pkgs, ... }:
let
inherit (config.boot.initrd) luks;
in
lib.mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) {
mobile.boot.stage-1 = {
bootConfig = {
luksDevices = luks.devices;
};
kernel = {
modules = [
"dm_mod" "dm_crypt" "cryptd" "input_leds"
] ++ luks.cryptoModules
;
};
extraUtils = [
{ package = pkgs.cryptsetup; }
# dmsetup is required for device mapper stuff to work in stage-1.
{ package = lib.getBin pkgs.lvm2; binaries = [
"lvm" "dmsetup"
];}
];
};
}

View File

@ -30,6 +30,7 @@
./initrd-vendor.nix ./initrd-vendor.nix
./initrd.nix ./initrd.nix
./internal.nix ./internal.nix
./luks.nix
./mobile-device.nix ./mobile-device.nix
./nixpkgs.nix ./nixpkgs.nix
./quirks ./quirks

View File

@ -16,11 +16,17 @@ let
install_package = set: install_package = set:
let let
pkg = if set ? type && set.type == "derivation" then set else set.package; pkg = if set ? type && set.type == "derivation" then set else set.package;
binaries = if set ? binaries then set.binaries else [ "*" ];
in in
'' (concat (map (path: ''
for BIN in ${pkg}/{s,}bin/*; do for BIN in ${pkg}/{s,}bin/${path}; do
copy_bin_and_libs $BIN if [ -e "$BIN" ]; then
copy_bin_and_libs "$BIN"
fi
done done
'') binaries ))
+
''
${if set ? extraCommand then set.extraCommand else ""} ${if set ? extraCommand then set.extraCommand else ""}
''; '';
install_packages = concat(map (install_package) packages); install_packages = concat(map (install_package) packages);

View File

@ -18,8 +18,8 @@ mrbgems.mkGem {
src = fetchFromGitHub { src = fetchFromGitHub {
repo = "mruby-lvgui"; repo = "mruby-lvgui";
owner = "mobile-nixos"; owner = "mobile-nixos";
rev = "ab7cf5b1b2e318a4bf5fc973507eb842dce80214"; rev = "f1bb1dd9b2c5aa3d3df4fcc41ca706f426d182a8";
sha256 = "09h9f3xlbvxdwpmzpf7whq0gphiv68842shy03ld712fw25393jx"; sha256 = "0ybjkzg743d21rn3q0vi0fa9zwp3ym9zw2q5ym24wc7gxdspjcjs";
}; };
gemBuildInputs = [ gemBuildInputs = [

View File

@ -24,13 +24,13 @@ let
in in
stdenv.mkDerivation { stdenv.mkDerivation {
pname = "lvgui"; pname = "lvgui";
version = "2020-07-25"; version = "2020-11-01";
src = fetchFromGitHub { src = fetchFromGitHub {
repo = "lvgui"; repo = "lvgui";
owner = "mobile-nixos"; owner = "mobile-nixos";
rev = "0dc257d07271fad023a5e6e9ac42222d2397c5cf"; rev = "4f8af498a81bd669d42ce3b370fc66fe4ec681b5";
sha256 = "1zb7naamqfzcsfi5809c93f1ygxp4w3aiw6172bpmnk1vxdchwsh"; sha256 = "00rik18c3c3l4glzh2azg90cwvp56s4wnski86rsn00bxslia5ma";
}; };
# Document `LVGL_ENV_SIMULATOR` in the built headers. # Document `LVGL_ENV_SIMULATOR` in the built headers.