Compare commits

..

3 Commits

Author SHA1 Message Date
Frédéric Tronel
0d772f59de Improve display of help and list certificate. 2025-12-30 11:02:43 +01:00
Frédéric Tronel
9f98cd1e70 Add a command to create a self-signed certificate. 2025-12-30 10:57:46 +01:00
Frédéric Tronel
8d709213d3 Improve README. 2025-12-29 15:48:56 +01:00
2 changed files with 94 additions and 30 deletions

View File

@@ -2,43 +2,67 @@
I wanted to install a certificate generated by Let's encrypt on my brand new laser printer
(HP Laserjet MFJ 4302) for the integrated administration webserver.
After research on the web, I discovered
After research on the web, I discovered that although there were projects supporting older models
nothing was existing for newer models.
Hence I decided to write my own tool in Python.
It was designed by reverse engineering the integrated administration webserver following the
network exchange when using the menu dedicated to certificate management
(Security -> Certicates management)
# Arguments
# Command
positional arguments:
{csr,pem} command help
csr Create CSR
pem Install certificate
```bash
{list, del, csr,pem}
list List certificates known by the printer
del Delete a certificate
csr Create a certificate signing request (CSR)
pem Install a PEM certificate
options:
-h, --help show this help message and exit
Common options:
-h, --help Show this help message and exit
-d, --debug Activate debug.
-c, --config CONFIG_FILENAME
Configuration file.
-u, --user USERNAME Username.
-p, --password [PASSWORD]
Password.
-c, --config CONFIG_FILENAME Configuration file.
-u, --user USERNAME Username (admin).
-p, --password [PASSWORD] Specify admin password.
-H, --host HOSTNAME Hostname.
-n, --no-tls-verification
Verify certificate validity.
-n, --no-tls-verification Do not verify certificate validity.
```
# How to use it ?
To obtain a CSR:
```
After playing a lot with the administration web server, I discover a reproducible way to convince
the printer to accept a Let's Encrypt certificate.
First you need to ask a CSR generated by the printer:
```bash
./refresh-certificate.py -c ./config.ini -n -p PASSWORD csr
```
This way the private key part of the certificate is only known by the printer.
I was not able to import the private key of a Let's Encrypt certificate as obtained when generating
the certificate directly from ACME.
To obtain a PEM certificate from Let's encrypt:
Next you need to ask Let's Encrypt to sign the CSR:
```
certbot certonly --webroot -w /var/www/letsencrypt/ --csr printer.csr
```
To install the PEM file on the printer:
You should obtain three PEM files in response:
1. _cert.pem_: the certificate itself (PEM format)
2. _chain.pem_: the intermediate CA that signed the certificate (PEM format)
3. _fullchain.pem_: the two previous files concatenated.
Finally you can install the certificate PEM file on the printer:
```
./refresh-certificate.py -c ./config.ini -n -p PASSWORD pem
./refresh-certificate.py -c ./config.ini -n -p PASSWORD pem -i fullchain.pem
```
Please note that during the last step, you can add either the _cert.pem_ file or
the _fullchain.pem_ files. In both cases, you should be able to connect to the printer webserver
without warning message from your navigator (tested on Firefox and Chromium).
This is possible because the main navigators comes with a certificates store that contains
not only the root certificate of Let's Encrypt but also the intermediate certificates.
However, note that most others tools will fail to connect to the Web server of the printer
(including this tool) because the printer will only present the final certificate.
You really have to install the _fullchain.pem_ to remove TLS connexion errors from all tools.

View File

@@ -41,6 +41,7 @@ from cryptography import x509
from cryptography.hazmat.primitives.serialization import Encoding
from dateutil.parser import parse
from typeguard import typechecked
from datetime import datetime, timedelta
import requests
import coloredlogs
@@ -344,6 +345,40 @@ def install_certificate(hostname: str, verify: bool, username: str, password: st
logger.info('Certificate successfully installed.')
@typechecked
def self_signed(hostname:str, verify:bool, username: str, password: str, bearer=None) -> None:
logger = logging.getLogger(__name__)
base_url = f'https://{hostname}'
if bearer is None:
bearer = get_bearer(hostname, verify, username, password)
now = datetime.now()
end = now + timedelta(days=365)
end = end.strftime('%Y-%m-%dT%H:%M:%S.000Z')
url = base_url+'/cdm/certificate/v1/certificates/selfSignedCertificate'
certificate = {"version":"1.1.0","signatureAlgorithm":"sha256WithRsa",
"subjectAlternativeNameList":[hostname], "privateKeyExportable": False,
"links":[{"rel":"certificateConstraints",
"href":"/cdm/certificate/v1/certificates/selfSignedCertificate/constraints",
"hints":None}],
"certificateAttributes":{"commonName":hostname},
"keyInfo":{"keyType":"rsa","keyStrength":"bits2048"},
"validity":{"toDate":end},"state":"processing"}
headers = { 'Authorization': f'Bearer {bearer}' }
r = requests.patch(url, headers=headers, data=json.dumps(certificate), verify=verify,
timeout=10)
if r.status_code != 204:
logger.error('Impossible to create a self-signed certificate. Error code: %d',
r.status_code)
sys.exit(-1)
logger.info('Self-signed certificate created successfully.')
@typechecked
def get_certificates(hostname:str, verify:bool, username: str, password: str,
@@ -496,13 +531,15 @@ def main():
subparsers = parser.add_subparsers(dest='command', required=True, help='command help')
subparsers.add_parser('list', help='List certificates')
subparsers.add_parser('list', help='List certificates.')
parser_delete = subparsers.add_parser('del', help='Delete a certificate')
subparsers.add_parser('self', help='Create a self-signed certificate.')
parser_delete = subparsers.add_parser('del', help='Delete a certificate.')
parser_delete.add_argument("-#", "--number", dest='certid', required=True, type=int,
help="Certificate number as given by list command.")
parser_create_csr = subparsers.add_parser('csr', help='Create CSR')
parser_create_csr = subparsers.add_parser('csr', help='Create a certificate signing request.')
parser_create_csr.add_argument("-C", "--country", dest='country', required=False, default='US',
help="Country.")
parser_create_csr.add_argument("-s", "--state", dest='state', required=False, help="State.")
@@ -514,7 +551,7 @@ def main():
parser_create_csr.add_argument("-o", "--output", dest='output', required=False, default=None,
help="Output file.")
parser_install_certificate = subparsers.add_parser('pem', help='Install certificate')
parser_install_certificate = subparsers.add_parser('pem', help='Install a certificate.')
parser_install_certificate.add_argument("-i", "--input", dest='input', required=True,
default=None, help="Input file.")
@@ -602,8 +639,8 @@ def main():
subject = cert.get('subject')
issuer = cert.get('issuer')
validity = cert.get('validity')
begin = parse(validity.get('fromDate'))
end = parse(validity.get('toDate'))
begin = parse(validity.get('fromDate')).strftime('%Y-%m-%d (%H:%M)')
end = parse(validity.get('toDate')).strftime('%Y-%m-%d (%H:%M)')
print(f'{certid} - {subject} issued by {issuer}. From: {begin} to {end}')
case 'del':
bearer = get_bearer(hostname=args.hostname, verify=args.verify, username=args.username,
@@ -624,6 +661,9 @@ def main():
case 'pem':
install_certificate(hostname=args.hostname, verify=args.verify, username=args.username,
password=args.password, filename=args.input)
case 'self':
self_signed(hostname=args.hostname, verify=args.verify, username=args.username,
password=args.password)
case _:
logger.error('Unknown command: %s', args.command)
sys.exit(-1)