Support for webauthn and u2f->webauthn migrations

This commit is contained in:
Daniel García
2021-06-07 23:34:00 +02:00
parent cea7a30d82
commit c380d9c379
18 changed files with 655 additions and 127 deletions

177
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,8 +28,8 @@ syslog = "4.0.1"
[dependencies]
# Web framework for nightly with a focus on ease-of-use, expressibility, and speed.
rocket = { version = "0.5.0-dev", features = ["tls"], default-features = false }
rocket_contrib = "0.5.0-dev"
rocket = { version = "=0.5.0-dev", features = ["tls"], default-features = false }
rocket_contrib = "=0.5.0-dev"
# HTTP client
reqwest = { version = "0.11.3", features = ["blocking", "json", "gzip", "brotli", "socks", "cookies"] }
@@ -41,7 +41,7 @@ bytes = "1.0.1"
url = "2.2.2"
# multipart/form-data support
multipart = { version = "0.17.1", features = ["server"], default-features = false }
multipart = { version = "0.18.0", features = ["server"], default-features = false }
# WebSockets library
ws = { version = "0.10.0", package = "parity-ws" }
@@ -77,7 +77,7 @@ uuid = { version = "0.8.2", features = ["v4"] }
# Date and time libraries
chrono = { version = "0.4.19", features = ["serde"] }
chrono-tz = "0.5.3"
time = "0.2.26"
time = "0.2.27"
# Job scheduler
job_scheduler = "1.2.1"
@@ -93,6 +93,7 @@ jsonwebtoken = "7.2.0"
# U2F library
u2f = "0.2.0"
webauthn-rs = "0.3.0-alpha.7"
# Yubico Library
yubico = { version = "0.10.0", features = ["online-tokio"], default-features = false }
@@ -101,7 +102,7 @@ yubico = { version = "0.10.0", features = ["online-tokio"], default-features = f
dotenv = { version = "0.15.0", default-features = false }
# Lazy initialization
once_cell = "1.7.2"
once_cell = "1.8.0"
# Numerical libraries
num-traits = "0.2.14"
@@ -109,10 +110,10 @@ num-derive = "0.3.3"
# Email libraries
tracing = { version = "0.1.26", features = ["log"] } # Needed to have lettre trace logging used when SMTP_DEBUG is enabled.
lettre = { version = "0.10.0-rc.1", features = ["smtp-transport", "builder", "serde", "native-tls", "hostname", "tracing"], default-features = false }
lettre = { version = "0.10.0-rc.3", features = ["smtp-transport", "builder", "serde", "native-tls", "hostname", "tracing"], default-features = false }
# Template library
handlebars = { version = "3.5.5", features = ["dir_source"] }
handlebars = { version = "4.0.0", features = ["dir_source"] }
# For favicon extraction from main website
html5ever = "0.25.1"
@@ -129,10 +130,10 @@ percent-encoding = "2.1.0"
idna = "0.2.3"
# CLI argument parsing
pico-args = "0.4.1"
pico-args = "0.4.2"
# Logging panics to logfile instead stderr only
backtrace = "0.3.59"
backtrace = "0.3.60"
# Macro ident concatenation
paste = "1.0.5"
@@ -151,3 +152,6 @@ data-url = { git = 'https://github.com/servo/rust-url', package="data-url", rev
# In particular, `cron` has since implemented parsing of some common syntax
# that wasn't previously supported (https://github.com/zslayton/cron/pull/64).
job_scheduler = { git = 'https://github.com/jjlin/job_scheduler', rev = 'ee023418dbba2bfe1e30a5fd7d937f9e33739806' }
# Add support for U2F appid extension compatibility
webauthn-rs = { git = 'https://github.com/dani-garcia/webauthn-rs', rev = '70b458f246d207e5b6333ada9f1d5794c5e01da1' }

View File

@@ -44,8 +44,8 @@
# https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE #######################
{% set vault_version = "2.19.0d" %}
{% set vault_image_digest = "sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233" %}
{% set vault_version = "2.20.4" %}
{% set vault_image_digest = "sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b" %}
# The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later

View File

@@ -14,15 +14,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.19.0d
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
# [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
# $ docker pull vaultwarden/web-vault:v2.20.4
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.20.4
# [vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
# [vaultwarden/web-vault:v2.19.0d]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b
# [vaultwarden/web-vault:v2.20.4]
#
FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
FROM vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b as vault
########################## BUILD IMAGE ##########################
FROM rust:1.51 as build

View File

@@ -14,15 +14,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.19.0d
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
# [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
# $ docker pull vaultwarden/web-vault:v2.20.4
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.20.4
# [vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
# [vaultwarden/web-vault:v2.19.0d]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b
# [vaultwarden/web-vault:v2.20.4]
#
FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
FROM vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b as vault
########################## BUILD IMAGE ##########################
FROM clux/muslrust:nightly-2021-04-14 as build

View File

@@ -14,15 +14,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.19.0d
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
# [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
# $ docker pull vaultwarden/web-vault:v2.20.4
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.20.4
# [vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
# [vaultwarden/web-vault:v2.19.0d]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b
# [vaultwarden/web-vault:v2.20.4]
#
FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
FROM vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b as vault
########################## BUILD IMAGE ##########################
FROM rust:1.51 as build

View File

@@ -14,15 +14,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.19.0d
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
# [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
# $ docker pull vaultwarden/web-vault:v2.20.4
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.20.4
# [vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
# [vaultwarden/web-vault:v2.19.0d]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b
# [vaultwarden/web-vault:v2.20.4]
#
FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
FROM vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b as vault
########################## BUILD IMAGE ##########################
FROM rust:1.51 as build

View File

@@ -14,15 +14,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.19.0d
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
# [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
# $ docker pull vaultwarden/web-vault:v2.20.4
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.20.4
# [vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
# [vaultwarden/web-vault:v2.19.0d]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b
# [vaultwarden/web-vault:v2.20.4]
#
FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
FROM vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b as vault
########################## BUILD IMAGE ##########################
FROM rust:1.51 as build

View File

@@ -14,15 +14,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to.
# - From the command line:
# $ docker pull vaultwarden/web-vault:v2.19.0d
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.19.0d
# [vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233]
# $ docker pull vaultwarden/web-vault:v2.20.4
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2.20.4
# [vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b]
#
# - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233
# [vaultwarden/web-vault:v2.19.0d]
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b
# [vaultwarden/web-vault:v2.20.4]
#
FROM vaultwarden/web-vault@sha256:a7bd6bc4db33bd45f723c4b1ac90918b7f80204560683cfc8efd9efd03a9b233 as vault
FROM vaultwarden/web-vault@sha256:810919341388a50d3a88225ce234333f72eb80382953997e9fd5590cca829e1b as vault
########################## BUILD IMAGE ##########################
FROM messense/rust-musl-cross:armv7-musleabihf as build

View File

@@ -17,6 +17,7 @@ pub mod authenticator;
pub mod duo;
pub mod email;
pub mod u2f;
pub mod webauthn;
pub mod yubikey;
pub fn routes() -> Vec<Route> {
@@ -26,6 +27,7 @@ pub fn routes() -> Vec<Route> {
routes.append(&mut duo::routes());
routes.append(&mut email::routes());
routes.append(&mut u2f::routes());
routes.append(&mut webauthn::routes());
routes.append(&mut yubikey::routes());
routes

View File

@@ -94,13 +94,14 @@ struct RegistrationDef {
}
#[derive(Serialize, Deserialize)]
struct U2FRegistration {
id: i32,
name: String,
pub struct U2FRegistration {
pub id: i32,
pub name: String,
#[serde(with = "RegistrationDef")]
reg: Registration,
counter: u32,
pub reg: Registration,
pub counter: u32,
compromised: bool,
pub migrated: Option<bool>,
}
impl U2FRegistration {
@@ -168,6 +169,7 @@ fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn)
reg: registration,
compromised: false,
counter: 0,
migrated: None,
};
let mut regs = get_u2f_registrations(&user.uuid, &conn)?.1;
@@ -273,6 +275,7 @@ fn get_u2f_registrations(user_uuid: &str, conn: &DbConn) -> Result<(bool, Vec<U2
reg: old_regs.remove(0),
compromised: false,
counter: 0,
migrated: None,
}];
// Save new format

File diff suppressed because it is too large Load Diff

View File

@@ -240,6 +240,7 @@ fn twofactor_auth(
_tf::authenticator::validate_totp_code_str(user_uuid, twofactor_code, &selected_data?, ip, conn)?
}
Some(TwoFactorType::U2f) => _tf::u2f::validate_u2f_login(user_uuid, twofactor_code, conn)?,
Some(TwoFactorType::Webauthn) => _tf::webauthn::validate_webauthn_login(user_uuid, twofactor_code, conn)?,
Some(TwoFactorType::YubiKey) => _tf::yubikey::validate_yubikey_login(twofactor_code, &selected_data?)?,
Some(TwoFactorType::Duo) => {
_tf::duo::validate_duo_login(data.username.as_ref().unwrap(), twofactor_code, conn)?
@@ -309,6 +310,11 @@ fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> Api
});
}
Some(TwoFactorType::Webauthn) if CONFIG.domain_set() => {
let request = two_factor::webauthn::generate_webauthn_login(user_uuid, conn)?;
result["TwoFactorProviders2"][provider.to_string()] = request.0;
}
Some(TwoFactorType::Duo) => {
let email = match User::find_by_uuid(user_uuid, &conn) {
Some(u) => u.email,

View File

@@ -51,10 +51,10 @@ impl NumberOrString {
}
}
fn into_i32(self) -> ApiResult<i32> {
fn into_i32(&self) -> ApiResult<i32> {
use std::num::ParseIntError as PIE;
match self {
NumberOrString::Number(n) => Ok(n),
NumberOrString::Number(n) => Ok(*n),
NumberOrString::String(s) => {
s.parse().map_err(|e: PIE| crate::Error::new("Can't convert to number", e.to_string()))
}

View File

@@ -114,7 +114,7 @@ macro_rules! db_run {
};
// Different code for each db
( $conn:ident: $( $($db:ident),+ $body:block )+ ) => {
( $conn:ident: $( $($db:ident),+ $body:block )+ ) => {{
#[allow(unused)] use diesel::prelude::*;
match $conn {
$($(
@@ -128,7 +128,7 @@ macro_rules! db_run {
$body
},
)+)+
}
}}
};
// Same for all dbs

View File

@@ -31,11 +31,14 @@ pub enum TwoFactorType {
U2f = 4,
Remember = 5,
OrganizationDuo = 6,
Webauthn = 7,
// These are implementation details
U2fRegisterChallenge = 1000,
U2fLoginChallenge = 1001,
EmailVerificationChallenge = 1002,
WebauthnRegisterChallenge = 1003,
WebauthnLoginChallenge = 1004,
}
/// Local methods
@@ -146,4 +149,73 @@ impl TwoFactor {
.map_res("Error deleting twofactors")
}}
}
pub fn migrate_u2f_to_webauthn(conn: &DbConn) -> EmptyResult {
let u2f_factors = db_run! { conn: {
twofactor::table
.filter(twofactor::atype.eq(TwoFactorType::U2f as i32))
.load::<TwoFactorDb>(conn)
.expect("Error loading twofactor")
.from_db()
}};
use crate::api::core::two_factor::u2f::U2FRegistration;
use crate::api::core::two_factor::webauthn::{get_webauthn_registrations, WebauthnRegistration};
use std::convert::TryInto;
use webauthn_rs::proto::*;
for mut u2f in u2f_factors {
let mut regs: Vec<U2FRegistration> = serde_json::from_str(&u2f.data)?;
// If there are no registrations or they are migrated (we do the migration in batch so we can consider them all migrated when the first one is)
if regs.is_empty() || regs[0].migrated == Some(true) {
continue;
}
let (_, mut webauthn_regs) = get_webauthn_registrations(&u2f.user_uuid, &conn)?;
// If the user already has webauthn registrations saved, don't overwrite them
if !webauthn_regs.is_empty() {
continue;
}
for reg in &mut regs {
let x: [u8; 32] = reg.reg.pub_key[1..33].try_into().unwrap();
let y: [u8; 32] = reg.reg.pub_key[33..65].try_into().unwrap();
let key = COSEKey {
type_: COSEAlgorithm::ES256,
key: COSEKeyType::EC_EC2(COSEEC2Key {
curve: ECDSACurve::SECP256R1,
x,
y,
}),
};
let new_reg = WebauthnRegistration {
id: reg.id,
migrated: true,
name: reg.name.clone(),
credential: Credential {
counter: reg.counter,
verified: false,
cred: key,
cred_id: reg.reg.key_handle.clone(),
registration_policy: UserVerificationPolicy::Discouraged,
},
};
webauthn_regs.push(new_reg);
reg.migrated = Some(true);
}
u2f.data = serde_json::to_string(&regs)?;
u2f.save(&conn)?;
TwoFactor::new(u2f.user_uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(&webauthn_regs)?)
.save(&conn)?;
}
Ok(())
}
}

View File

@@ -39,19 +39,18 @@ use diesel::ConnectionError as DieselConErr;
use diesel_migrations::RunMigrationsError as DieselMigErr;
use handlebars::RenderError as HbErr;
use jsonwebtoken::errors::Error as JwtErr;
use lettre::address::AddressError as AddrErr;
use lettre::error::Error as LettreErr;
use lettre::transport::smtp::Error as SmtpErr;
use regex::Error as RegexErr;
use reqwest::Error as ReqErr;
use serde_json::{Error as SerdeErr, Value};
use std::io::Error as IoErr;
use std::time::SystemTimeError as TimeErr;
use u2f::u2ferror::U2fError as U2fErr;
use webauthn_rs::error::WebauthnError as WebauthnErr;
use yubico::yubicoerror::YubicoError as YubiErr;
use lettre::address::AddressError as AddrErr;
use lettre::error::Error as LettreErr;
use lettre::transport::smtp::Error as SmtpErr;
#[derive(Serialize)]
pub struct Empty {}
@@ -86,6 +85,7 @@ make_error! {
DieselConError(DieselConErr): _has_source, _api_error,
DieselMigError(DieselMigErr): _has_source, _api_error,
WebauthnError(WebauthnErr): _has_source, _api_error,
}
impl std::fmt::Debug for Error {

View File

@@ -60,6 +60,8 @@ fn main() {
let pool = create_db_pool();
schedule_jobs(pool.clone());
crate::db::models::TwoFactor::migrate_u2f_to_webauthn(&pool.get().unwrap()).unwrap();
launch_rocket(pool, extra_debug); // Blocks until program termination.
}