T09 · Insecure Skill Coding Practices
Error
- Location
- identity.py:31
- Finding
- Private keys may be stored unencrypted with inherited filesystem permissions<![CDATA[ ## Vulnerability Details **File Location**: `identity.py:31-55` and `identity.py:86-110` **Vulnerability Type**: Insecure private-key storage **Risk Level**: High ### Vulnerable Code ```python if password: # Encrypt private key private_pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.BestAvailableEncryption(password.encode()) ) else: # No encryption (for convenience, but less secure) private_pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption() ) public_pem = public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ) private_path = os.path.join(KEY_DIR, f"{name}_private.pem") public_path = os.path.join(KEY_DIR, f"{name}_public.pem") with open(private_path, "wb") as f: f.write(private_pem) with open(public_path, "wb") as f: f.write(public_pem) ``` The same storage pattern is used for RSA keys at lines 86-110. ### Technical Analysis When `--password` is omitted, the private key is serialized using `serialization.NoEncryption()`. The resulting plaintext PEM is then created with Python's normal `open()` function without explicitly enforcing owner-only permissions. The final permissions therefore depend on the process umask and surrounding environment. Under a permissive umask, another local account or process may be able to read the private key. Existing destination files are also opened with truncation rather than exclusive creation. Although unencrypted key generation is documented as less secure, protecting a signing identity requires secure defaults. Plaintext storage and implicit permissions do not provide adequate protection for sensitive private-key material. ### Attack Path 1. A user invo ...[truncated 930 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Encrypt private keys by default and require an explicit unsafe override for plaintext storage. - Prompt for passwords securely with `getpass.getpass()` instead of encouraging command-line password arguments. - Create private-key files atomically with owner-only mode `0600`, such as by using `os.open()` with `O_CREAT | O_EXCL` and an explicit mode. - Set restrictive permissions on the `keys` directory, such as `0700`. - Refuse to overwrite an existing private-key file unless the user provides an explicit, carefully validated overwrite option. - Verify the resulting file permissions after creation and abort if adequate protection cannot be established. - Consider integrating an operating-system credential store, hardware-backed key store, or dedicated secrets manager for production use. ]]>
