@@ -11,6 +11,7 @@ use base64::Engine;
1111use defguard_wireguard_rs:: {
1212 InterfaceConfiguration , WGApi , WireguardInterfaceApi , host:: Peer , key:: Key , net:: IpAddrMask ,
1313} ;
14+ use openssl:: symm:: { Cipher , Crypter , Mode } ;
1415use serde:: { Deserialize , Serialize } ;
1516use tokio:: time:: sleep;
1617use x25519_dalek:: { PublicKey , StaticSecret } ;
@@ -42,6 +43,7 @@ struct NetworkConfig {
4243#[ derive( Debug , Clone , Serialize , Deserialize ) ]
4344struct DaemonConfig {
4445 pub config_url : String ,
46+ pub secret : Option < String > ,
4547 pub fetch_interval : u64 ,
4648 pub interface_name : String ,
4749}
@@ -65,16 +67,57 @@ fn load_key() -> (PublicKey, StaticSecret) {
6567 ( pubkey, secret_key)
6668}
6769
70+ fn decrypt_config ( encrypted : & str , secret : & str ) -> Result < String , Box < dyn Error > > {
71+ // Step 1: Base64 decode
72+ let data = base64:: engine:: general_purpose:: STANDARD
73+ . decode ( encrypted)
74+ . unwrap ( ) ;
75+
76+ // Step 2: Verify prefix
77+ assert ! ( & data[ 0 ..8 ] == b"Salted__" ) ;
78+ let salt = & data[ 8 ..16 ] ;
79+ let ciphertext = & data[ 16 ..] ;
80+
81+ // Step 3: Derive key and iv using PBKDF2-HMAC-SHA256
82+ let mut key = [ 0u8 ; 32 ] ; // AES-256 => 32-byte key
83+ let mut iv = [ 0u8 ; 16 ] ; // CBC IV
84+ let mut derived = [ 0u8 ; 48 ] ; // key + iv = 48 bytes total
85+
86+ openssl:: pkcs5:: pbkdf2_hmac (
87+ secret. as_bytes ( ) ,
88+ salt,
89+ 10_000 ,
90+ openssl:: hash:: MessageDigest :: sha256 ( ) ,
91+ & mut derived,
92+ )
93+ . unwrap ( ) ;
94+ key. copy_from_slice ( & derived[ ..32 ] ) ;
95+ iv. copy_from_slice ( & derived[ 32 ..48 ] ) ;
96+
97+ // Step 4: Decrypt
98+ let cipher = Cipher :: aes_256_cbc ( ) ;
99+ let mut crypter = Crypter :: new ( cipher, Mode :: Decrypt , & key, Some ( & iv) ) . unwrap ( ) ;
100+ crypter. pad ( true ) ;
101+
102+ let mut plaintext = vec ! [ 0 ; ciphertext. len( ) + cipher. block_size( ) ] ;
103+ let mut count = crypter. update ( ciphertext, & mut plaintext) . unwrap ( ) ;
104+ count += crypter. finalize ( & mut plaintext[ count..] ) . unwrap ( ) ;
105+ plaintext. truncate ( count) ;
106+
107+ Ok ( String :: from_utf8 ( plaintext) ?)
108+ }
109+
68110async fn fetch_and_apply_config (
69111 wgapi : & WGApi ,
70112 interface_config : & mut InterfaceConfiguration ,
71113 pubkey : & str ,
72114 config : & DaemonConfig ,
73115) -> Result < ( ) , Box < dyn Error > > {
74- let r = reqwest:: get ( & config. config_url )
75- . await ?
76- . json :: < NetworkConfig > ( )
77- . await ?;
116+ let mut r = reqwest:: get ( & config. config_url ) . await ?. text ( ) . await ?;
117+ if config. secret . is_some ( ) {
118+ r = decrypt_config ( & r, & config. secret . as_ref ( ) . unwrap ( ) ) ?;
119+ }
120+ let r: NetworkConfig = serde_json:: from_str ( & r) ?;
78121 let mut my_config: Option < ServerConfig > = None ;
79122 for peer in r. peers . iter ( ) {
80123 if peer. pubkey == pubkey {
@@ -200,3 +243,23 @@ async fn main() {
200243 sleep ( Duration :: from_secs ( config. fetch_interval ) ) . await ;
201244 }
202245}
246+
247+ #[ cfg( test) ]
248+ mod test {
249+ use super :: * ;
250+
251+ #[ tokio:: test]
252+ async fn test_decrypt_config ( ) {
253+ let mut r = r#"
254+ U2FsdGVkX19nIrNUt9Wpcyw2qK2rEqJkHX6Wv7ot3sZGR5wIBtkHPvmBXkre46a4
255+ T+8hHiRtwvrZZithpFHi9Y1Tq+T7DrwT4A1auJ15ZZbRSEA5quEl/ywF/65FaDeA
256+ 5uhj5lr+BcO8bvLbT7dQzmpAP7rCzY0l067fQh6pNuaiDhK31XnZ0WIK/E+o5k+1
257+ +JwiloAjeMGdP5jNFTws+XjFTPYPJAfhIVdpGqfmb5+hFZh9rZsRTsb+TaGC0tWS
258+ UtXcZz6A4RmXWLx+YgEGUg=="#
259+ . replace ( "\n " , "" )
260+ . replace ( " " , "" ) ;
261+ r = decrypt_config ( & r, "mysecret" ) . unwrap ( ) ;
262+ let r: NetworkConfig = serde_json:: from_str ( & r) . unwrap ( ) ;
263+ println ! ( "{:?}" , r) ;
264+ }
265+ }
0 commit comments