OPENSSL-notes.mdโข8.5 kB
# OpenSSL s_client Tool - Detailed Analysis and Findings
## Overview
This document captures detailed findings from implementing cipher suite analysis using the OpenSSL `s_client` command on macOS. These insights are crucial for understanding how to properly integrate OpenSSL into automated TLS analysis tools.
## Key Discovery: Output Redirection Behavior
### Critical Finding: `-brief` Flag Redirects Output to STDERR
**Most Important Discovery**: When using the `-brief` flag with `openssl s_client`, **all output goes to STDERR, not STDOUT**.
```bash
# Without -brief: output goes to STDOUT
echo | openssl s_client -connect google.com:443 -tls1_3
# With -brief: output goes to STDERR
echo | openssl s_client -connect google.com:443 -tls1_3 -brief
```
**Example Output Analysis:**
```python
result = subprocess.run(['openssl', 's_client', '-connect', 'google.com:443', '-tls1_3', '-brief'],
capture_output=True, text=True, input='\n')
print(f"STDOUT: {repr(result.stdout)}") # ''
print(f"STDERR: {repr(result.stderr)}") # 'Connecting to...\nCONNECTION ESTABLISHED\n...'
```
**Impact**: This completely changes how you need to parse the output in automated tools.
**Solution**: Always check both stdout and stderr:
```python
output = result.stdout + result.stderr
success = 'CONNECTION ESTABLISHED' in output
```
## TLS Version Testing
### Supported Flags
OpenSSL s_client supports these TLS version flags:
- `-tls1` - TLS 1.0
- `-tls1_1` - TLS 1.1
- `-tls1_2` - TLS 1.2
- `-tls1_3` - TLS 1.3
### Version Detection Pattern
```bash
# Test TLS 1.3 support
echo | openssl s_client -connect hostname:443 -tls1_3 -brief
# Success indicators:
# - Return code: 0
# - Output contains: "CONNECTION ESTABLISHED"
# - Output contains: "Protocol version: TLSv1.3"
# Failure indicators:
# - Return code: 1
# - Error: "no protocols available"
# - Error codes like: "40C2220102000000:error:0A0000BF:SSL routines..."
```
### Real Examples
**TLS 1.3 Success (Google):**
```
Return code: 0
STDERR: 'Connecting to 142.250.69.46\nCONNECTION ESTABLISHED\nProtocol version: TLSv1.3\nCiphersuite: TLS_AES_256_GCM_SHA384\nPeer certificate: CN=*.google.com\nHash used: SHA256\nSignature type: ecdsa_secp256r1_sha256\nVerification: OK\nNegotiated TLS1.3 group: X25519MLKEM768\nDONE'
```
**TLS 1.1 Failure (Google):**
```
Return code: 1
STDERR: 'Connecting to 142.250.69.46\n40C2220102000000:error:0A0000BF:SSL routines:tls_setup_handshake:no protocols available:ssl/statem/statem_lib.c:155:\n'
```
## Cipher Suite Testing
### TLS 1.2 and Below Ciphers
Use the `-cipher` flag:
```bash
echo | openssl s_client -connect hostname:443 -tls1_2 -cipher 'ECDHE-RSA-AES256-GCM-SHA384' -brief
```
### TLS 1.3 Ciphers
Use the `-ciphersuites` flag (different from `-cipher`):
```bash
echo | openssl s_client -connect hostname:443 -tls1_3 -ciphersuites 'TLS_AES_256_GCM_SHA384' -brief
```
### Cipher Success Detection
Look for the exact cipher name in the output:
```
Ciphersuite: ECDHE-RSA-AES256-GCM-SHA384
```
### Cipher Failure Patterns
- Return code: non-zero
- Error: "no cipher match"
- Error: "Call to SSL_CONF_cmd(-cipher, CIPHER_NAME) failed"
## Input Handling
### Connection Termination
**Problem**: OpenSSL s_client waits for input by default, causing hanging.
**Solution**: Provide input to close the connection immediately:
```python
subprocess.run(cmd, input='\n', ...) # Send newline to close
```
### Timeout Management
**macOS Issue**: No built-in `timeout` command.
**Solution**: Use subprocess timeout:
```python
subprocess.run(cmd, timeout=8, ...) # 8 seconds is usually sufficient
```
## Error Patterns and Detection
### Success Conditions
```python
def is_successful_connection(result):
output = result.stdout + result.stderr
return (
result.returncode == 0 and
'CONNECTION ESTABLISHED' in output and
'Protocol version:' in output and
'no protocols available' not in output
)
```
### Common Error Patterns
1. **Protocol not supported**: `"no protocols available"`
2. **Cipher not supported**: `"no cipher match"`
3. **Connection timeout**: Process timeout exception
4. **Network issues**: Various connection errors
## Performance Considerations
### Timing
- TLS version check: ~1-2 seconds per version
- Cipher test: ~1-2 seconds per cipher
- Quick scan (20 ciphers): ~30-40 seconds
- Full scan (100+ ciphers): ~3-5 minutes
### Optimization Strategies
1. **Test TLS versions first** - Skip cipher tests for unsupported versions
2. **Prioritize modern ciphers** - Test TLS 1.3 and strong TLS 1.2 ciphers first
3. **Parallel testing** - Could potentially test multiple ciphers concurrently (with rate limiting)
4. **Reasonable timeouts** - 8 seconds per test is usually sufficient
## Getting Available Ciphers
### List All Ciphers
```bash
openssl ciphers 'ALL' # Returns colon-separated list
```
### TLS Version Specific
```bash
openssl ciphers -tls1_3 # TLS 1.3 ciphers
openssl ciphers -tls1_2 # TLS 1.2 ciphers
```
### Output Format
```
TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:...
```
## Command Construction Patterns
### Basic Connection Test
```bash
openssl s_client -connect hostname:port -brief
```
### TLS Version Specific
```bash
openssl s_client -connect hostname:port -tls1_3 -brief
openssl s_client -connect hostname:port -tls1_2 -brief
```
### Cipher Testing
```bash
# TLS 1.2 and below
openssl s_client -connect hostname:port -tls1_2 -cipher 'CIPHER_NAME' -brief
# TLS 1.3
openssl s_client -connect hostname:port -tls1_3 -ciphersuites 'TLS_CIPHER_NAME' -brief
```
## Integration Best Practices
### Robust Command Execution
```python
async def test_openssl_connection(hostname, port, args):
cmd = ['openssl', 's_client', '-connect', f'{hostname}:{port}'] + args + ['-brief']
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=8,
input='\n' # Close connection immediately
)
output = result.stdout + result.stderr # Check both!
return analyze_output(result.returncode, output)
except subprocess.TimeoutExpired:
return False
except FileNotFoundError:
raise Exception("OpenSSL not found - ensure it's installed and in PATH")
```
### Error Handling
```python
def analyze_output(returncode, output):
if returncode != 0:
return False
if 'no protocols available' in output:
return False
if 'CONNECTION ESTABLISHED' not in output:
return False
return True
```
## Limitations and Gotchas
### macOS Specific Issues
1. **No timeout command** - Must use subprocess timeout
2. **OpenSSL version differences** - Behavior may vary between OpenSSL versions
3. **Certificate verification** - Some systems may have certificate verification issues
### Rate Limiting
- **Be respectful**: Don't hammer servers with too many rapid connections
- **Add delays**: Consider adding small delays between tests for the same server
- **Implement backoff**: Retry with exponential backoff on failures
### Security Considerations
- **Server impact**: Cipher scanning can be detected as scanning behavior
- **Rate limiting**: Many servers implement rate limiting
- **Firewall rules**: Some networks may block or throttle SSL connections
## Debugging Tips
### Verbose Output
Remove `-brief` for full detailed output:
```bash
echo | openssl s_client -connect hostname:443 -tls1_3
```
### Manual Testing
Always test commands manually first:
```bash
echo | openssl s_client -connect google.com:443 -tls1_3 -brief
echo $? # Check return code
```
### Output Analysis
Print both stdout and stderr when debugging:
```python
print(f"Return code: {result.returncode}")
print(f"STDOUT: {repr(result.stdout)}")
print(f"STDERR: {repr(result.stderr)}")
```
## Conclusion
The key insight for successful OpenSSL integration is understanding that the `-brief` flag redirects output to STDERR. This single discovery resolved most integration issues and enabled reliable automated cipher suite analysis.
Other critical factors:
1. Proper input handling to avoid hanging connections
2. Appropriate timeout management
3. Different flag usage for TLS 1.3 vs earlier versions
4. Robust error detection patterns
These findings enable reliable, automated TLS cipher suite analysis using OpenSSL as the backend engine.