Skip to content

Commit cc27490

Browse files
authored
Feature/validate cert (#3)
* feat: impl naive cert validation * feat: impl allow insecure ssl cert in frontend
1 parent 773e1a1 commit cc27490

File tree

10 files changed

+205
-60
lines changed

10 files changed

+205
-60
lines changed

crates/openconnect-core/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,6 @@ chacha20poly1305 = { workspace = true }
3232
[target.'cfg(windows)'.dependencies]
3333
windows-sys = { workspace = true }
3434
windows = { workspace = true }
35+
36+
[[example]]
37+
name = "password_server"

crates/openconnect-core/src/main.rs renamed to crates/openconnect-core/examples/password_server.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ fn main() -> anyhow::Result<()> {
2424
.password(&env::var("VPN_PASSWORD").unwrap())
2525
.protocol(protocol)
2626
.enable_udp(true)
27+
.accept_insecure_cert(true)
2728
.build()?;
2829

2930
client.connect(entrypoint)?;
Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,65 @@
11
use crate::VpnClient;
22
use openconnect_sys::*;
3+
use std::ffi::{CStr, CString};
34

4-
pub static mut ACCEPTED_CERTS: *mut AcceptedCert = std::ptr::null_mut();
5-
5+
#[derive(Debug, PartialEq, Eq)]
66
pub struct AcceptedCert {
7-
next: *mut AcceptedCert,
8-
fingerprint: *mut ::std::os::raw::c_char,
9-
host: *const ::std::os::raw::c_char,
10-
port: i32,
7+
pub fingerprint: String,
8+
pub host: Option<String>,
9+
pub port: i32,
10+
}
11+
12+
#[derive(Debug, Default)]
13+
pub struct OpenSSLCert {
14+
pub accepted_certs: Vec<AcceptedCert>,
1115
}
1216

1317
pub extern "C" fn validate_peer_cert(
1418
_privdata: *mut ::std::os::raw::c_void,
1519
_reason: *const ::std::os::raw::c_char,
1620
) -> ::std::os::raw::c_int {
1721
let ctx = VpnClient::from_c_void(_privdata);
22+
let openssl_cert = unsafe { &mut (*ctx).certs.accepted_certs };
1823

1924
unsafe {
2025
let vpninfo = (*ctx).vpninfo;
21-
let _fingerprint = openconnect_get_peer_cert_hash(vpninfo);
22-
let mut this = ACCEPTED_CERTS;
26+
let host = (*ctx).get_hostname();
27+
let port = (*ctx).get_port();
28+
let peer_fingerprint = openconnect_get_peer_cert_hash(vpninfo);
2329

24-
while !this.is_null() {
25-
if (!(*this).host.is_null() || (*this).host == openconnect_get_hostname(vpninfo))
26-
&& ((*this).port == 0 || (*this).port == openconnect_get_port(vpninfo))
27-
{
28-
let err = openconnect_check_peer_cert_hash(vpninfo, (*this).fingerprint);
30+
for cert in openssl_cert.iter_mut().rev() {
31+
if (host.is_none() || cert.host == host) && (port == 0 || cert.port == port) {
32+
let fingerprint_in_cstr =
33+
CString::new(cert.fingerprint.as_str()).expect("Invalid fingerprint");
34+
let err = openconnect_check_peer_cert_hash(vpninfo, fingerprint_in_cstr.as_ptr());
2935
if err == 0 {
3036
return 0;
3137
}
3238
if err < 0 {
33-
println!("Certificate hash check failed");
39+
// TODO: log error
40+
println!("Could not check peer cert hash: {}", cert.fingerprint);
3441
}
3542
}
36-
this = (*this).next;
3743
}
38-
}
3944

40-
0
45+
// SAFETY: we should not use CString::from_raw(peer_fingerprint)
46+
// because peer_fingerprint will be deallocated in rust and cause a double free
47+
let fingerprint = CStr::from_ptr(peer_fingerprint)
48+
.to_string_lossy()
49+
.to_string();
50+
51+
if (*ctx).handle_accept_insecure_cert(&fingerprint) {
52+
let newcert = AcceptedCert {
53+
fingerprint,
54+
host,
55+
port,
56+
};
57+
openssl_cert.push(newcert);
58+
println!("User accepted insecure certificate");
59+
0
60+
} else {
61+
println!("User rejected insecure certificate");
62+
1
63+
}
64+
}
4165
}

crates/openconnect-core/src/config.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ pub struct Entrypoint {
7979
pub protocol: Protocol,
8080
pub cookie: Option<String>,
8181
pub enable_udp: bool,
82+
pub accept_insecure_cert: bool,
8283
}
8384

8485
pub struct EntrypointBuilder {
@@ -89,6 +90,7 @@ pub struct EntrypointBuilder {
8990
protocol: Option<Protocol>,
9091
cookie: Option<String>,
9192
enable_udp: bool,
93+
accept_insecure_cert: Option<bool>,
9294
}
9395

9496
impl EntrypointBuilder {
@@ -101,6 +103,7 @@ impl EntrypointBuilder {
101103
protocol: None,
102104
cookie: None,
103105
enable_udp: true,
106+
accept_insecure_cert: None,
104107
}
105108
}
106109

@@ -139,6 +142,11 @@ impl EntrypointBuilder {
139142
self
140143
}
141144

145+
pub fn accept_insecure_cert(&mut self, accept_insecure_cert: bool) -> &mut Self {
146+
self.accept_insecure_cert = Some(accept_insecure_cert);
147+
self
148+
}
149+
142150
pub fn build(&self) -> OpenconnectResult<Entrypoint> {
143151
let server = self
144152
.server
@@ -160,6 +168,7 @@ impl EntrypointBuilder {
160168
protocol,
161169
cookie: self.cookie.clone(),
162170
enable_udp: self.enable_udp,
171+
accept_insecure_cert: self.accept_insecure_cert.unwrap_or(false),
163172
})
164173
}
165174
}

crates/openconnect-core/src/events.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
use crate::{result::OpenconnectError, Status};
22
use std::sync::Arc;
33

4+
#[allow(clippy::type_complexity)]
45
#[derive(Clone)]
56
pub struct EventHandlers {
67
pub(crate) handle_connection_state_change: Option<Arc<dyn Fn(Status)>>,
8+
pub(crate) handle_peer_cert_invalid: Option<Arc<dyn Fn(&str) -> bool>>,
79
}
810

911
impl EventHandlers {
1012
pub fn new() -> Self {
1113
Self {
1214
handle_connection_state_change: None,
15+
handle_peer_cert_invalid: None,
1316
}
1417
}
1518

@@ -21,6 +24,15 @@ impl EventHandlers {
2124
self.handle_connection_state_change = Some(Arc::new(handler));
2225
self
2326
}
27+
28+
pub fn with_handle_peer_cert_invalid<F>(mut self, handler: F) -> Self
29+
where
30+
F: Fn(&str) -> bool,
31+
F: Send + 'static,
32+
{
33+
self.handle_peer_cert_invalid = Some(Arc::new(handler));
34+
self
35+
}
2436
}
2537

2638
impl Default for EventHandlers {

crates/openconnect-core/src/lib.rs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub mod storage;
1212
#[cfg(target_os = "macos")]
1313
pub use openconnect_sys::helper_reluanch_as_root;
1414

15+
use cert::OpenSSLCert;
1516
use config::{Config, Entrypoint, LogLevel};
1617
use events::{EventHandlers, Events};
1718
use form::FormContext;
@@ -46,6 +47,7 @@ pub struct VpnClient {
4647
callbacks: EventHandlers,
4748
entrypoint: RwLock<Option<Entrypoint>>,
4849
form_context: FormContext,
50+
certs: OpenSSLCert,
4951
}
5052

5153
unsafe impl Send for VpnClient {}
@@ -71,13 +73,13 @@ impl VpnClient {
7173
}
7274
}
7375

74-
pub(crate) extern "C" fn validate_peer_cert(
75-
_privdata: *mut ::std::os::raw::c_void,
76-
_reason: *const ::std::os::raw::c_char,
77-
) -> ::std::os::raw::c_int {
78-
println!("validate_peer_cert");
79-
0
80-
}
76+
// pub(crate) extern "C" fn validate_peer_cert(
77+
// _privdata: *mut ::std::os::raw::c_void,
78+
// _reason: *const ::std::os::raw::c_char,
79+
// ) -> ::std::os::raw::c_int {
80+
// println!("validate_peer_cert");
81+
// 0
82+
// }
8183

8284
pub(crate) extern "C" fn default_setup_tun_vfn(privdata: *mut ::std::os::raw::c_void) {
8385
let client = VpnClient::from_c_void(privdata);
@@ -155,6 +157,30 @@ impl VpnClient {
155157
println!("stats: {:?}, {:?}", dlts, stats);
156158
}
157159

160+
pub(crate) fn handle_accept_insecure_cert(&self, fingerprint: &str) -> bool {
161+
let entrypoint = self.entrypoint.read();
162+
let accept_in_entrypoint_config = {
163+
if let Ok(entrypoint) = entrypoint {
164+
(*entrypoint)
165+
.as_ref()
166+
.map(|entrypoint| entrypoint.accept_insecure_cert)
167+
.unwrap_or(false)
168+
} else {
169+
false
170+
}
171+
};
172+
173+
if accept_in_entrypoint_config {
174+
return true;
175+
}
176+
177+
if let Some(ref handler) = self.callbacks.handle_peer_cert_invalid {
178+
handler(fingerprint)
179+
} else {
180+
false
181+
}
182+
}
183+
158184
pub fn set_loglevel(&self, level: LogLevel) {
159185
unsafe {
160186
openconnect_set_loglevel(self.vpninfo, level as i32);
@@ -408,6 +434,7 @@ impl Connectable for VpnClient {
408434
callbacks,
409435
entrypoint: RwLock::new(None),
410436
form_context: FormContext::default(),
437+
certs: OpenSSLCert::default(),
411438
});
412439

413440
let instance = Arc::into_raw(instance) as *mut VpnClient; // dangerous, leak for assign to vpninfo
@@ -423,7 +450,7 @@ impl Connectable for VpnClient {
423450

424451
let vpninfo = openconnect_vpninfo_new(
425452
useragent.as_ptr(),
426-
Some(Self::validate_peer_cert),
453+
Some(cert::validate_peer_cert),
427454
None,
428455
Some(FormContext::process_auth_form_cb),
429456
Some(helper_format_vargs), // format args on C side

crates/openconnect-core/src/storage.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ pub struct OidcServer {
7878
pub issuer: String,
7979
pub client_id: String,
8080
pub client_secret: Option<String>,
81+
pub allow_insecure: Option<bool>,
8182
pub updated_at: Option<String>,
8283
}
8384

@@ -88,6 +89,7 @@ pub struct PasswordServer {
8889
pub server: String,
8990
pub username: String,
9091
pub password: Option<String>,
92+
pub allow_insecure: Option<bool>,
9193
pub updated_at: Option<String>,
9294
}
9395

@@ -102,6 +104,7 @@ impl PasswordServer {
102104
server: self.server.clone(),
103105
username: self.username.clone(),
104106
password,
107+
allow_insecure: self.allow_insecure,
105108
updated_at: self.updated_at.clone(),
106109
}
107110
}
@@ -116,6 +119,7 @@ impl PasswordServer {
116119
server: self.server.clone(),
117120
username: self.username.clone(),
118121
password,
122+
allow_insecure: self.allow_insecure,
119123
updated_at: self.updated_at.clone(),
120124
}
121125
}
@@ -381,6 +385,7 @@ async fn test_save_config() {
381385
issuer: "https://example.com".to_string(),
382386
client_id: "client_id".to_string(),
383387
client_secret: Some("client_secret".to_string()),
388+
allow_insecure: Some(true),
384389
updated_at: None,
385390
});
386391

@@ -411,6 +416,7 @@ async fn test_config_type() {
411416
issuer: "https://example.com".to_string(),
412417
client_id: "client_id".to_string(),
413418
client_secret: None,
419+
allow_insecure: Some(true),
414420
updated_at: None,
415421
});
416422

@@ -425,6 +431,7 @@ async fn test_config_type() {
425431
server: "https://example.com".to_string(),
426432
username: "username".to_string(),
427433
password: Some("password".to_string()),
434+
allow_insecure: Some(true),
428435
updated_at: None,
429436
});
430437

0 commit comments

Comments
 (0)