Actually validate door controller SSL certificates

This commit is contained in:
Adam Goldsmith 2020-06-01 02:02:26 -04:00
parent 4d38ac5840
commit 0528686903
5 changed files with 52 additions and 9 deletions

View File

@ -33,3 +33,15 @@ Retrieves events from the HID Evo Solo door controllers, and pushes them to a SQ
## Systemd
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.

View File

@ -6,6 +6,8 @@ doorControllers:
Wood Shop Rear: {ip: 172.18.51.15, access: Wood Shop}
Storage Closet: {ip: 172.18.51.16, access: Storage Closet}
doorControllerCA_BUNDLE: "hidglobal.com.pem"
# {member type: door schedule}
memberLevels:
CMS Staff: 7x24

24
hidglobal.com.pem Normal file
View 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-----

View File

@ -22,6 +22,7 @@ class Config:
self.DOOR_PASSWORD,
name=doorName,
access=doorData["access"],
cert=self.doorControllerCA_BUNDLE
)
for doorName, doorData in self.doorControllers.items()
}

View File

@ -3,9 +3,10 @@ from datetime import datetime
from io import StringIO
import requests
import urllib3
from lxml import etree
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 = 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
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class HostNameIgnoringAdapter(HTTPAdapter):
def init_poolmanager(self, *args, **kwargs):
super().init_poolmanager(*args, **kwargs, assert_hostname=False)
class RemoteError(Exception):
@ -33,12 +35,16 @@ class RemoteError(Exception):
class DoorController:
def __init__(self, ip, username, password, name="", access=""):
def __init__(self, ip, username, password, name="", access="", cert=None):
self.ip = ip
self.username = username
self.password = password
self.name = name
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
@property
@ -55,13 +61,12 @@ class DoorController:
def doImport(self, params=None, files=None):
"""Send a request to the door control import script"""
r = requests.post(
r = self.session.post(
"https://" + self.ip + "/cgi-bin/import.cgi",
params=params,
files=files,
auth=requests.auth.HTTPDigestAuth(self.username, self.password),
timeout=60,
verify=False,
) # ignore insecure SSL
xml = etree.XML(r.content)
if (
@ -82,11 +87,10 @@ class DoorController:
def doXMLRequest(self, xml, prefix=b'<?xml version="1.0" encoding="UTF-8"?>'):
if not isinstance(xml, bytes):
xml = etree.tostring(xml)
r = requests.get(
r = self.session.get(
"https://" + self.ip + "/cgi-bin/vertx_xml.cgi",
params={"XML": prefix + xml},
auth=requests.auth.HTTPDigestAuth(self.username, self.password),
verify=False,
)
resp_xml = etree.XML(r.content)
# probably meed to be more sane about this