Insecure Private Key Storage
Summary
BDServer encrypts ActivityPub signing keys with a password, then stores both the encrypted key and the password in plaintext in the same JSON file. This provides no security benefit: anyone who can read the account file obtains everything needed to decrypt the private key.
Vulnerability Details
| Property | Value |
|---|---|
| Type | Insecure Cryptographic Storage |
| CVSS | 4.4 (Medium) |
| CWE | CWE-522 (Insufficiently Protected Credentials) |
Root Cause
The GenerateSSLKeys() function generates RSA keypairs for ActivityPub HTTP Signatures:
def GenerateSSLKeys():
encryptor = rsa.generate_private_key(public_exponent=65537, key_size=2048)
password = RandString(200).encode()
pem = encryptor.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(password)
)
# ...
return password.decode(), pem.decode(), pub.decode()
The private key is encrypted using PKCS8 with a 200-character random password. However, GetSSLKeys() stores both the encrypted key and the password in the same account file:
activity_pub["password"] = password # Plaintext password
activity_pub["pem"] = pem # Encrypted private key
activity_pub["pub"] = pub # Public key
The resulting account JSON structure:
{
"username": "admin",
"password": "a]H#k...",
"activity_pub": {
"password": "X7KM2P9N...",
"pem": "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIF...",
"pub": "-----BEGIN PUBLIC KEY-----\nMIIB..."
}
}
The encryption adds complexity without security. An attacker who reads the file gets both the encrypted key and the password to decrypt it.
Remediation
Securing private keys at rest is a difficult problem. Real solutions involve hardware security modules, cloud key management services, or prompting for a master password on startup. These are overkill for a self-hosted blog server.
The practical fix is to remove the encryption entirely. Store the private key in plaintext PEM format. This is honest about the security model: the private key is protected by file system permissions, not a password stored next to it. The current approach adds complexity without security, creating a false sense of protection.
Disclaimer
This assessment was performed on a best-effort basis against BDServer commit dc86854 and reflects the state of the software at the time of testing. The findings and remediations are provided for informational purposes and should be independently validated before implementation. This report does not guarantee all vulnerabilities have been identified, nor does it guarantee the suggested fixes will be effective in all environments.