Actually validate door controller SSL certificates
This commit is contained in:
parent
4d38ac5840
commit
0528686903
12
README.md
12
README.md
@ -33,3 +33,15 @@ Retrieves events from the HID Evo Solo door controllers, and pushes them to a SQ
|
|||||||
## Systemd
|
## Systemd
|
||||||
|
|
||||||
There are systemd units in the [`systemd`](./systemd/) folder, which can be used to run the various scripts regularly.
|
There are systemd units in the [`systemd`](./systemd/) folder, which can be used to run the various scripts regularly.
|
||||||
|
|
||||||
|
## SSL Certificates
|
||||||
|
|
||||||
|
The HID Evo Solo door controllers we use have a self-signed certificate, which is included in this repo as [`hidglobal.com.pem`](./hidglobal.com.pem).
|
||||||
|
|
||||||
|
If you need to use a different certificate, you can either download it with your browser or the following command (replacing `SERVER` by the address of the door):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
openssl s_client -connect SERVER:443 </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > example.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
Then set `doorControllerCA_BUNDLE` in `config.yaml` to the path to the created pem file. If your doors have different certificates, (which ours annoyingly don't), you will need to concatenate the certificates together into a single file.
|
||||||
|
@ -6,6 +6,8 @@ doorControllers:
|
|||||||
Wood Shop Rear: {ip: 172.18.51.15, access: Wood Shop}
|
Wood Shop Rear: {ip: 172.18.51.15, access: Wood Shop}
|
||||||
Storage Closet: {ip: 172.18.51.16, access: Storage Closet}
|
Storage Closet: {ip: 172.18.51.16, access: Storage Closet}
|
||||||
|
|
||||||
|
doorControllerCA_BUNDLE: "hidglobal.com.pem"
|
||||||
|
|
||||||
# {member type: door schedule}
|
# {member type: door schedule}
|
||||||
memberLevels:
|
memberLevels:
|
||||||
CMS Staff: 7x24
|
CMS Staff: 7x24
|
||||||
|
24
hidglobal.com.pem
Normal file
24
hidglobal.com.pem
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIID9TCCAt2gAwIBAgIJAPBotjnyfGu6MA0GCSqGSIb3DQEBCwUAMIGQMQswCQYD
|
||||||
|
VQQGEwJVUzERMA8GA1UECAwIQ29sb3JhZG8xFDASBgNVBAcMC1dlc3RtaW5zdGVy
|
||||||
|
MQwwCgYDVQQKDANISUQxDDAKBgNVBAsMA05BUzEWMBQGA1UEAwwNaGlkZ2xvYmFs
|
||||||
|
LmNvbTEkMCIGCSqGSIb3DQEJARYVc3VwcG9ydEBoaWRnbG9iYWwuY29tMB4XDTE5
|
||||||
|
MDcyMjEwMTQxMFoXDTI5MDcxOTEwMTQxMFowgZAxCzAJBgNVBAYTAlVTMREwDwYD
|
||||||
|
VQQIDAhDb2xvcmFkbzEUMBIGA1UEBwwLV2VzdG1pbnN0ZXIxDDAKBgNVBAoMA0hJ
|
||||||
|
RDEMMAoGA1UECwwDTkFTMRYwFAYDVQQDDA1oaWRnbG9iYWwuY29tMSQwIgYJKoZI
|
||||||
|
hvcNAQkBFhVzdXBwb3J0QGhpZGdsb2JhbC5jb20wggEiMA0GCSqGSIb3DQEBAQUA
|
||||||
|
A4IBDwAwggEKAoIBAQDSibuXB9Tn0EdwL2jDig26s/b1D9SX5B4xnZM+xZ4/mE6U
|
||||||
|
Meg5xbiTSMiWqtoSMVxG1WJDxogJWxCgZis2qk3AG89PBarg17pBmxPLYyCLricx
|
||||||
|
alyNvTJBxYgA/zKagPof6h6UqKOkhsW9qvulEmPe+TKk47pmlZXe+v+1A6PQDY5B
|
||||||
|
Y3MtqE23cnZ5nBTVanFAc1vbokMXUCCtRvE1Y/KhvuaJr2VjOSJ/KV3vcdTSCLGc
|
||||||
|
W9/n/Fv8udvI/eIkoPNpCUwngm8j3Aa7qN/OSg3SvVvBcl/Ykc08STSyZPMJGBaR
|
||||||
|
EuUcAraEBZbUDOCinDS488jKVHAXhrnvzzi7RMlhAgMBAAGjUDBOMB0GA1UdDgQW
|
||||||
|
BBSvOzxCjQi86ZUsuW0o4aa7GNAeSTAfBgNVHSMEGDAWgBSvOzxCjQi86ZUsuW0o
|
||||||
|
4aa7GNAeSTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA/8FCv6x8v
|
||||||
|
PHm2Ya/hbZ/S3amdl7/E1illeApNRZodTGCn/rlVSGanCfWYzEY2naHiDC2ImhZq
|
||||||
|
NkHK9uvUtxaVQFq5VN6WQMo351J78LLfcoqpKOLGX3b9byFvrw7WporZx3C7yL1U
|
||||||
|
LS3oxI/pgavxy1KbOIw/yl+QgV50vlfvQ7sKZ1E5YOrgWLP5nJ9OeEKRdsASJyZS
|
||||||
|
Jjl0k/eGaZreSvAZPmx4kaePfbi7DNDA+mNhSFygwt6AakjjVoF2xUZ1F+qwBtER
|
||||||
|
GPxdZWldywUYsdBRG1PPvBsMo9ME46HpPdXRIjMge8P01fsaMr/6H86ojWg9uJmH
|
||||||
|
cCKtiouo08hL
|
||||||
|
-----END CERTIFICATE-----
|
@ -22,6 +22,7 @@ class Config:
|
|||||||
self.DOOR_PASSWORD,
|
self.DOOR_PASSWORD,
|
||||||
name=doorName,
|
name=doorName,
|
||||||
access=doorData["access"],
|
access=doorData["access"],
|
||||||
|
cert=self.doorControllerCA_BUNDLE
|
||||||
)
|
)
|
||||||
for doorName, doorData in self.doorControllers.items()
|
for doorName, doorData in self.doorControllers.items()
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,10 @@ from datetime import datetime
|
|||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import urllib3
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from lxml.builder import ElementMaker
|
from lxml.builder import ElementMaker
|
||||||
|
from requests import Session
|
||||||
|
from requests.adapters import HTTPAdapter
|
||||||
|
|
||||||
E_plain = ElementMaker(nsmap={"hid": "http://www.hidglobal.com/VertX"})
|
E_plain = ElementMaker(nsmap={"hid": "http://www.hidglobal.com/VertX"})
|
||||||
E = ElementMaker(
|
E = ElementMaker(
|
||||||
@ -22,9 +23,10 @@ fieldnames = "CardNumber,CardFormat,PinRequired,PinCode,ExtendedAccess,ExpiryDat
|
|||||||
","
|
","
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: where should this live?
|
|
||||||
# it's fine, ssl certs are for losers anyway
|
class HostNameIgnoringAdapter(HTTPAdapter):
|
||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
def init_poolmanager(self, *args, **kwargs):
|
||||||
|
super().init_poolmanager(*args, **kwargs, assert_hostname=False)
|
||||||
|
|
||||||
|
|
||||||
class RemoteError(Exception):
|
class RemoteError(Exception):
|
||||||
@ -33,12 +35,16 @@ class RemoteError(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class DoorController:
|
class DoorController:
|
||||||
def __init__(self, ip, username, password, name="", access=""):
|
def __init__(self, ip, username, password, name="", access="", cert=None):
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.username = username
|
self.username = username
|
||||||
self.password = password
|
self.password = password
|
||||||
self.name = name
|
self.name = name
|
||||||
self.access = access
|
self.access = access
|
||||||
|
self.session = Session()
|
||||||
|
if cert is not None:
|
||||||
|
self.session.mount("https://", HostNameIgnoringAdapter())
|
||||||
|
self.session.verify = cert
|
||||||
|
|
||||||
# lazy evaluated, hopefully won't change for the lifetime of this object
|
# lazy evaluated, hopefully won't change for the lifetime of this object
|
||||||
@property
|
@property
|
||||||
@ -55,13 +61,12 @@ class DoorController:
|
|||||||
|
|
||||||
def doImport(self, params=None, files=None):
|
def doImport(self, params=None, files=None):
|
||||||
"""Send a request to the door control import script"""
|
"""Send a request to the door control import script"""
|
||||||
r = requests.post(
|
r = self.session.post(
|
||||||
"https://" + self.ip + "/cgi-bin/import.cgi",
|
"https://" + self.ip + "/cgi-bin/import.cgi",
|
||||||
params=params,
|
params=params,
|
||||||
files=files,
|
files=files,
|
||||||
auth=requests.auth.HTTPDigestAuth(self.username, self.password),
|
auth=requests.auth.HTTPDigestAuth(self.username, self.password),
|
||||||
timeout=60,
|
timeout=60,
|
||||||
verify=False,
|
|
||||||
) # ignore insecure SSL
|
) # ignore insecure SSL
|
||||||
xml = etree.XML(r.content)
|
xml = etree.XML(r.content)
|
||||||
if (
|
if (
|
||||||
@ -82,11 +87,10 @@ class DoorController:
|
|||||||
def doXMLRequest(self, xml, prefix=b'<?xml version="1.0" encoding="UTF-8"?>'):
|
def doXMLRequest(self, xml, prefix=b'<?xml version="1.0" encoding="UTF-8"?>'):
|
||||||
if not isinstance(xml, bytes):
|
if not isinstance(xml, bytes):
|
||||||
xml = etree.tostring(xml)
|
xml = etree.tostring(xml)
|
||||||
r = requests.get(
|
r = self.session.get(
|
||||||
"https://" + self.ip + "/cgi-bin/vertx_xml.cgi",
|
"https://" + self.ip + "/cgi-bin/vertx_xml.cgi",
|
||||||
params={"XML": prefix + xml},
|
params={"XML": prefix + xml},
|
||||||
auth=requests.auth.HTTPDigestAuth(self.username, self.password),
|
auth=requests.auth.HTTPDigestAuth(self.username, self.password),
|
||||||
verify=False,
|
|
||||||
)
|
)
|
||||||
resp_xml = etree.XML(r.content)
|
resp_xml = etree.XML(r.content)
|
||||||
# probably meed to be more sane about this
|
# probably meed to be more sane about this
|
||||||
|
Reference in New Issue
Block a user