5353base_path = os .path .dirname (os .path .abspath (__file__ ))
5454
5555
56+ KS_OBFUSCATE = - 1
57+ KS_PASSWORD = 0
58+
5659class encryptContentPlugin (BasePlugin ):
5760 """ Plugin that encrypt markdown content with AES and inject decrypt form. """
5861
@@ -140,16 +143,33 @@ def __sign_file__(self, fname, url, key):
140143 signer = eddsa .new (key , 'rfc8032' )
141144 return base64 .b64encode (signer .sign (h )).decode ()
142145
143- def __encrypt_key__ (self , key , password , iterations , id , user = '' ):
146+ def __add_to_keystore__ (self , index , key , id , password , user = '' ):
147+ keystore = self .setup ['keystore' ]
148+ store_id = id
149+
150+ if index not in keystore :
151+ new_entry = {}
152+ new_entry [store_id ] = key .hex ()
153+ keystore [index ] = new_entry
154+ else :
155+ keystore [index ][store_id ] = key .hex ()
156+
157+ def __encrypt_keys_from_keystore__ (self , index ):
158+ keystore = self .setup ['keystore' ]
159+ password = index [1 ]
160+ if index [0 ] == KS_OBFUSCATE :
161+ iterations = 1
162+ else :
163+ iterations = self .setup ['kdf_iterations' ]
144164 """ Encrypts key with PBKDF2 and AES-256. """
145165 salt = get_random_bytes (16 )
146166 # generate PBKDF2 key from salt and password (password is URI encoded)
147- kdfkey = PBKDF2 (quote (user + password , safe = '~()*!\' ' ), salt , 32 , count = iterations , hmac_hash_module = SHA256 )
167+ kdfkey = PBKDF2 (quote (password , safe = '~()*!\' ' ), salt , 32 , count = iterations , hmac_hash_module = SHA256 )
148168 # initialize AES-256
149169 iv = get_random_bytes (16 )
150170 cipher = AES .new (kdfkey , AES .MODE_CBC , iv )
151171 # use it to encrypt the AES-256 key
152- plaintext = key + '#' . encode () + quote ( self . config [ 'remember_suffix' ] + str ( id ), safe = '~()*! \' ' ).encode ()
172+ plaintext = json . dumps ( keystore [ index ] ).encode ()
153173 # plaintext must be padded to be a multiple of 16 bytes
154174 plaintext_padded = pad (plaintext , 16 , style = 'pkcs7' )
155175 ciphertext = cipher .encrypt (plaintext_padded )
@@ -161,11 +181,21 @@ def __encrypt_key__(self, key, password, iterations, id, user=''):
161181 if self .setup ['min_enttropy_secret' ] == 0 or enttropy_secret < self .setup ['min_enttropy_secret' ]:
162182 self .setup ['min_enttropy_secret' ] = enttropy_secret
163183
164- return (
165- base64 .b64encode (iv ).decode () ,
166- base64 .b64encode (ciphertext ).decode (),
167- base64 .b64encode (salt ).decode ()
168- )
184+ if isinstance (index [0 ], str ):
185+ userhash = quote (index [0 ], safe = '~()*!\' ' ).encode () # safe transform username analogous to encodeURIComponent
186+ userhash = SHA256 .new (userhash ).digest () # sha256 sum of username
187+ return (
188+ base64 .b64encode (iv ).decode () ,
189+ base64 .b64encode (ciphertext ).decode (),
190+ base64 .b64encode (salt ).decode (),
191+ base64 .b64encode (userhash ).decode () # base64 encode userhash
192+ )
193+ else :
194+ return (
195+ base64 .b64encode (iv ).decode () ,
196+ base64 .b64encode (ciphertext ).decode (),
197+ base64 .b64encode (salt ).decode ()
198+ )
169199
170200 def __encrypt_text__ (self , text , key ):
171201 """ Encrypts text with AES-256. """
@@ -198,28 +228,34 @@ def __encrypt_content__(self, content, base_path, encryptcontent_path, encryptco
198228 encryptcontent_id = ''
199229
200230 if encryptcontent ['type' ] == 'password' :
201- # get 32-bit AES-256 key from password_keystore
231+ # get 32-bit AES-256 key from password_keys
202232 key = encryptcontent ['key' ]
203- keystore = self .setup ['password_keystore ' ][encryptcontent ['password' ]]
204- encryptcontent_id = quote ( self . config [ 'remember_suffix' ] + str ( keystore ['id' ]), safe = '~()*! \' ' )
205- encryptcontent_keystore = keystore [ 'store ' ]
233+ keystore = self .setup ['password_keys ' ][encryptcontent ['password' ]]
234+ encryptcontent_id = keystore ['id' ]
235+ encrypted_keystore = self . setup [ 'keystore_password ' ]
206236 elif encryptcontent ['type' ] == 'level' :
207- # get 32-bit AES-256 key from password_keystore
237+ # get 32-bit AES-256 key from level_keys
208238 key = encryptcontent ['key' ]
209- keystore = self .setup ['level_keystore' ][encryptcontent ['level' ]]
210- encryptcontent_id = quote (self .config ['remember_suffix' ] + str (keystore ['id' ]), safe = '~()*!\' ' )
211- encryptcontent_keystore = keystore ['store' ]
239+ keystore = self .setup ['level_keys' ][encryptcontent ['level' ]]
240+ encryptcontent_id = keystore ['id' ]
212241 if keystore .get ('uname' ):
242+ encrypted_keystore = self .setup ['keystore_userpass' ]
213243 uname = 1
244+ else :
245+ encrypted_keystore = self .setup ['keystore_password' ]
214246 elif encryptcontent ['type' ] == 'obfuscate' :
215- # get 32-bit AES-256 key from password_keystore
247+ # get 32-bit AES-256 key from obfuscate_keys
216248 key = encryptcontent ['key' ]
217- keystore = self .setup ['obfuscate_keystore ' ][encryptcontent ['obfuscate' ]]
218- encryptcontent_id = quote ( self . config [ 'remember_suffix' ] + str ( keystore ['id' ]), safe = '~()*! \' ' )
219- encryptcontent_keystore = keystore [ 'store ' ]
249+ keystore = self .setup ['obfuscate_keys ' ][encryptcontent ['obfuscate' ]]
250+ encryptcontent_id = keystore ['id' ]
251+ encrypted_keystore = self . setup [ 'keystore_obfuscate ' ]
220252 obfuscate = 1
221253 obfuscate_password = encryptcontent ['obfuscate' ]
222254
255+ encryptcontent_keystore = []
256+ for entry in encrypted_keystore :
257+ encryptcontent_keystore .append (encrypted_keystore [entry ])
258+
223259 inject_something = encryptcontent ['inject' ] if 'inject' in encryptcontent else None
224260 delete_something = encryptcontent ['delete_id' ] if 'delete_id' in encryptcontent else None
225261
@@ -401,9 +437,14 @@ def on_config(self, config, **kwargs):
401437
402438 self .setup ['kdf_iterations' ] = pow (10 ,self .config ['kdf_pow' ])
403439
404- self .setup ['password_keystore' ] = {}
405- self .setup ['obfuscate_keystore' ] = {}
406- self .setup ['level_keystore' ] = {}
440+ self .setup ['password_keys' ] = {}
441+ self .setup ['obfuscate_keys' ] = {}
442+ self .setup ['level_keys' ] = {}
443+
444+ self .setup ['keystore' ] = {}
445+ self .setup ['keystore_password' ] = {}
446+ self .setup ['keystore_userpass' ] = {}
447+ self .setup ['keystore_obfuscate' ] = {}
407448
408449 if self .config ['password_file' ]:
409450 if self .config ['password_inventory' ]:
@@ -417,31 +458,22 @@ def on_config(self, config, **kwargs):
417458 for level in self .config ['password_inventory' ].keys ():
418459 new_entry = {}
419460 self .keystore_id += 1
420- new_entry ['id' ] = self .keystore_id
461+ new_entry ['id' ] = quote ( self .config [ 'remember_suffix' ] + str ( self . keystore_id ), safe = '~()*! \' ' )
421462 new_entry ['key' ] = get_random_bytes (32 )
422463 credentials = self .config ['password_inventory' ][level ]
423464 if isinstance (credentials , list ):
424- new_entry ['store' ] = []
425465 for password in credentials :
426466 if isinstance (password , dict ):
427467 logger .error ("Configuration error in yaml syntax of 'password_inventory': expected string at level '{level}', but found dict!" .format (level = level ))
428468 os ._exit (1 )
429- keystore = self .__encrypt_key__ (new_entry ['key' ], password , self .setup ['kdf_iterations' ], self .keystore_id )
430- new_entry ['store' ].append (';' .join (keystore ))
469+ self .__add_to_keystore__ ((KS_PASSWORD ,password ), new_entry ['key' ], new_entry ['id' ], password )
431470 elif isinstance (credentials , dict ):
432- new_entry ['store' ] = []
433471 for user in credentials :
434472 new_entry ['uname' ] = user
435- keystore = self .__encrypt_key__ (new_entry ['key' ], credentials [user ], self .setup ['kdf_iterations' ], self .keystore_id , user ) # add username to password
436- userhash = quote (user , safe = '~()*!\' ' ).encode () # safe transform username analogous to encodeURIComponent
437- userhash = SHA256 .new (userhash ).digest () # sha256 sum of username
438- userhash = base64 .b64encode (userhash ).decode () # base64 encode userhash
439- new_entry ['store' ].append (';' .join (keystore + (userhash ,)))
473+ self .__add_to_keystore__ ((user ,credentials [user ]), new_entry ['key' ], new_entry ['id' ], credentials [user ], user )
440474 else :
441- keystore = self .__encrypt_key__ (new_entry ['key' ], credentials , self .setup ['kdf_iterations' ], self .keystore_id )
442- new_entry ['store' ] = []
443- new_entry ['store' ].append (';' .join (keystore ))
444- self .setup ['level_keystore' ][level ] = new_entry
475+ self .__add_to_keystore__ ((KS_PASSWORD ,password ), new_entry ['key' ], new_entry ['id' ], credentials )
476+ self .setup ['level_keys' ][level ] = new_entry
445477
446478 if self .config ['sign_files' ]:
447479 sign_key_path = self .setup ['config_path' ].joinpath (self .config ['sign_key' ])
@@ -548,7 +580,7 @@ def on_page_markdown(self, markdown, page, config, **kwargs):
548580 # Set global password as default password for each page
549581 encryptcontent ['password' ] = self .config ['global_password' ]
550582 # level '_global' will be set as global level
551- if '_global' in self .setup ['level_keystore ' ]:
583+ if '_global' in self .setup ['level_keys ' ]:
552584 encryptcontent ['level' ] = '_global'
553585
554586 if 'password' in page .meta .keys ():
@@ -594,34 +626,30 @@ def on_page_markdown(self, markdown, page, config, **kwargs):
594626 del page .meta ['encryption_info_message' ]
595627
596628 if encryptcontent .get ('password' ):
597- if encryptcontent ['password' ] not in self .setup ['password_keystore ' ]:
629+ if encryptcontent ['password' ] not in self .setup ['password_keys ' ]:
598630 new_entry = {}
599631 self .keystore_id += 1
600- new_entry ['id' ] = self .keystore_id
632+ new_entry ['id' ] = quote ( self .config [ 'remember_suffix' ] + str ( self . keystore_id ), safe = '~()*! \' ' )
601633 new_entry ['key' ] = get_random_bytes (32 )
602- keystore = self .__encrypt_key__ (new_entry ['key' ], encryptcontent ['password' ], self .setup ['kdf_iterations' ], self .keystore_id )
603- new_entry ['store' ] = []
604- new_entry ['store' ].append (';' .join (keystore ))
605- self .setup ['password_keystore' ][encryptcontent ['password' ]] = new_entry
634+ self .__add_to_keystore__ ((KS_PASSWORD ,password ), new_entry ['key' ], new_entry ['id' ], encryptcontent ['password' ])
635+ self .setup ['password_keys' ][encryptcontent ['password' ]] = new_entry
606636 encryptcontent ['type' ] = 'password'
607- encryptcontent ['key' ] = self .setup ['password_keystore ' ][encryptcontent ['password' ]]['key' ]
637+ encryptcontent ['key' ] = self .setup ['password_keys ' ][encryptcontent ['password' ]]['key' ]
608638 setattr (page , 'encryptcontent' , encryptcontent )
609639 elif encryptcontent .get ('level' ):
610640 encryptcontent ['type' ] = 'level'
611- encryptcontent ['key' ] = self .setup ['level_keystore ' ][encryptcontent ['level' ]]['key' ]
641+ encryptcontent ['key' ] = self .setup ['level_keys ' ][encryptcontent ['level' ]]['key' ]
612642 setattr (page , 'encryptcontent' , encryptcontent )
613643 elif encryptcontent .get ('obfuscate' ):
614- if encryptcontent ['obfuscate' ] not in self .setup ['obfuscate_keystore ' ]:
644+ if encryptcontent ['obfuscate' ] not in self .setup ['obfuscate_keys ' ]:
615645 new_entry = {}
616646 self .keystore_id += 1
617- new_entry ['id' ] = self .keystore_id
647+ new_entry ['id' ] = quote ( self .config [ 'remember_suffix' ] + str ( self . keystore_id ), safe = '~()*! \' ' )
618648 new_entry ['key' ] = get_random_bytes (32 )
619- keystore = self .__encrypt_key__ (new_entry ['key' ], encryptcontent ['obfuscate' ], 1 , self .keystore_id )
620- new_entry ['store' ] = []
621- new_entry ['store' ].append (';' .join (keystore ))
622- self .setup ['obfuscate_keystore' ][encryptcontent ['obfuscate' ]] = new_entry
649+ self .__add_to_keystore__ ((KS_OBFUSCATE ,encryptcontent ['obfuscate' ]), new_entry ['key' ], new_entry ['id' ], encryptcontent ['obfuscate' ])
650+ self .setup ['obfuscate_keys' ][encryptcontent ['obfuscate' ]] = new_entry
623651 encryptcontent ['type' ] = 'obfuscate'
624- encryptcontent ['key' ] = self .setup ['obfuscate_keystore ' ][encryptcontent ['obfuscate' ]]['key' ]
652+ encryptcontent ['key' ] = self .setup ['obfuscate_keys ' ][encryptcontent ['obfuscate' ]]['key' ]
625653 setattr (page , 'encryptcontent' , encryptcontent )
626654
627655 return markdown
@@ -664,6 +692,20 @@ def on_page_context(self, context, page, config, **kwargs):
664692 :param nav: global navigation object
665693 :return: dict of template context variables
666694 """
695+
696+ # Encrypt all keys to keystore
697+ # It just encrypts once, but needs to run on every page
698+ for index in self .setup ['keystore' ]:
699+ if index [0 ] == KS_OBFUSCATE :
700+ if index not in self .setup ['keystore_obfuscate' ]:
701+ self .setup ['keystore_obfuscate' ][index ] = ';' .join (self .__encrypt_keys_from_keystore__ (index ))
702+ elif index [0 ] == KS_PASSWORD :
703+ if index not in self .setup ['keystore_password' ]:
704+ self .setup ['keystore_password' ][index ] = ';' .join (self .__encrypt_keys_from_keystore__ (index ))
705+ else :
706+ if index not in self .setup ['keystore_userpass' ]:
707+ self .setup ['keystore_userpass' ][index ] = ';' .join (self .__encrypt_keys_from_keystore__ (index ))
708+
667709 if hasattr (page , 'encryptcontent' ):
668710 if 'i18n_page_file_locale' in context :
669711 locale = context ['i18n_page_file_locale' ]
@@ -806,6 +848,7 @@ def on_post_build(self, config, **kwargs):
806848
807849 :param config: global configuration object
808850 """
851+
809852 Path (config .data ["site_dir" ] + '/assets/javascripts/' ).mkdir (parents = True , exist_ok = True )
810853 decrypt_js_path = Path (config .data ["site_dir" ] + '/assets/javascripts/decrypt-contents.js' )
811854 with open (decrypt_js_path , "w" ) as file :
@@ -826,9 +869,14 @@ def on_post_build(self, config, **kwargs):
826869 new_entry ['url' ] = "https:" + jsurl [0 ]
827870 self .setup ['files_to_sign' ].append (new_entry )
828871
829- self .setup ['password_keystore' ].clear ()
830- self .setup ['obfuscate_keystore' ].clear ()
831- self .setup ['level_keystore' ].clear ()
872+ #clear all keystores
873+ self .setup ['password_keys' ].clear ()
874+ self .setup ['obfuscate_keys' ].clear ()
875+ self .setup ['level_keys' ].clear ()
876+ self .setup ['keystore' ].clear ()
877+ self .setup ['keystore_obfuscate' ].clear ()
878+ self .setup ['keystore_password' ].clear ()
879+ self .setup ['keystore_userpass' ].clear ()
832880
833881 #modify search_index in the style of mkdocs-exclude-search
834882 if self .setup ['search_plugin_found' ] and self .config ['search_index' ] != 'clear' :
0 commit comments