Skip to content
Snippets Groups Projects
Commit a3a9306c authored by Xavier Jouslin de Noray's avatar Xavier Jouslin de Noray Committed by Adrien Béraud
Browse files

Chain: add chain verification for plugins

Change-Id: I019749e2bc19ae3343bb0edffc47353bb6b3a025
parent 28e8206b
Branches
No related tags found
No related merge requests found
...@@ -9,5 +9,6 @@ config.mak ...@@ -9,5 +9,6 @@ config.mak
/foo/ /foo/
/.vscode/ /.vscode/
*.crt *.crt
!**/resources/*.crt
*.key *.key
*.sign *.sign
#
# Copyright (C) 2023 Savoir-faire Linux Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Creates packaging targets for a distribution and architecture.
# This helps reduce the length of the top Makefile.
#
from abc import ABC from abc import ABC
from typing import List, Tuple, Optional from typing import List, Tuple, Optional
from OpenSSL import crypto from OpenSSL import crypto
......
#
# Copyright (C) 2023 Savoir-faire Linux Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Creates packaging targets for a distribution and architecture.
# This helps reduce the length of the top Makefile.
#
from abc import ABC
from abstractCertificate import AbstractCertificate
class AbstractChain(ABC):
@staticmethod
def load(file_path: str) -> 'AbstractChain':
pass
@staticmethod
def load(content: bytes) -> 'AbstractChain':
pass
def verify(self) -> bool:
pass
def _add_trusted_cert(self, cert: 'AbstractCertificate') -> None:
pass
def _add_trusted_crl(self, cert: 'AbstractCertificate') -> None:
pass
\ No newline at end of file
...@@ -30,7 +30,7 @@ from pluginSignature import PluginSignature ...@@ -30,7 +30,7 @@ from pluginSignature import PluginSignature
from certificate import Certificate from certificate import Certificate
from certificateRevocationList import CertificateRevocationList from certificateRevocationList import CertificateRevocationList
from pluginCertificate import PluginCertificate from pluginCertificate import PluginCertificate
from abstactCertificate import AbstractCertificate from abstractCertificate import AbstractCertificate
from signature import Signature from signature import Signature
def parse() ->argparse.Namespace: def parse() ->argparse.Namespace:
...@@ -145,11 +145,7 @@ def sign(path_to_file: str, issuer: str, plugin: bool, req: bool) -> Union[bytes ...@@ -145,11 +145,7 @@ def sign(path_to_file: str, issuer: str, plugin: bool, req: bool) -> Union[bytes
def verify(path_to_file: str, issuer: str, plugin: bool, req: bool, is_archive: bool) -> bool: def verify(path_to_file: str, issuer: str, plugin: bool, req: bool, is_archive: bool) -> bool:
if plugin: if plugin:
return PluginSignature(None).verify_from_archive( return PluginCertificate.verify_from_archive(path_to_file) if is_archive else PluginCertificate.verify(path_to_file)
path_to_file
) if is_archive else PluginSignature(
None
).verify()
if req: if req:
return CertificateSigningRequest(None, None).verify_from_archive(path_to_file) if is_archive else CertificateSigningRequest.load(path_to_file).verify() return CertificateSigningRequest(None, None).verify_from_archive(path_to_file) if is_archive else CertificateSigningRequest.load(path_to_file).verify()
if not req: if not req:
......
...@@ -26,7 +26,7 @@ from random import SystemRandom ...@@ -26,7 +26,7 @@ from random import SystemRandom
from typing import Optional, Tuple, List from typing import Optional, Tuple, List
from signature import Signature from signature import Signature
from abstactCertificate import AbstractCertificate from abstractCertificate import AbstractCertificate
from utils import save, read, save_archive, read_archive from utils import save, read, save_archive, read_archive
class Certificate(AbstractCertificate): class Certificate(AbstractCertificate):
......
...@@ -24,7 +24,7 @@ from datetime import datetime ...@@ -24,7 +24,7 @@ from datetime import datetime
from OpenSSL import crypto from OpenSSL import crypto
from certificate import Certificate from certificate import Certificate
from abstactCertificate import AbstractCertificate from abstractCertificate import AbstractCertificate
from utils import read, save, save_archive from utils import read, save, save_archive
...@@ -61,6 +61,16 @@ class CertificateRevocationList(AbstractCertificate): ...@@ -61,6 +61,16 @@ class CertificateRevocationList(AbstractCertificate):
crl.add_revoked(revoked) crl.add_revoked(revoked)
return CertificateRevocationList(None, crl, issuer).sign(issuer) return CertificateRevocationList(None, crl, issuer).sign(issuer)
@staticmethod
def load(issuer: 'Certificate') -> 'CertificateRevocationList':
"""
Load a certificate from a certificate.
:param issuer: The certificate.
:return: A Certificate object.
"""
crl = crypto.load_crl(crypto.FILETYPE_PEM, read(f'{issuer}.crl', 'rb'))
return CertificateRevocationList(None, crl, issuer)
@staticmethod @staticmethod
def load(file_path: str, issuer: str) -> 'CertificateRevocationList': def load(file_path: str, issuer: str) -> 'CertificateRevocationList':
""" """
...@@ -115,3 +125,13 @@ class CertificateRevocationList(AbstractCertificate): ...@@ -115,3 +125,13 @@ class CertificateRevocationList(AbstractCertificate):
if self._cert is None: if self._cert is None:
raise ValueError raise ValueError
save_archive(path_2_file, crypto.dump_crl(crypto.FILETYPE_PEM, self._cert), 'wb') save_archive(path_2_file, crypto.dump_crl(crypto.FILETYPE_PEM, self._cert), 'wb')
def verify(self, subject: 'Certificate') -> bool:
"""
Verify the certificate.
:param issuer: The issuer certificate.
:return: True if the certificate is valid, False otherwise.
"""
if self._cert is None:
raise ValueError
return self._cert.to_cryptography().get_revoked_certificate_by_serial_number(subject.cert[1].get_serial_number()) is not None
...@@ -27,7 +27,7 @@ from zipfile import ZipFile ...@@ -27,7 +27,7 @@ from zipfile import ZipFile
from utils import read, save, save_archive, read_archive from utils import read, save, save_archive, read_archive
from abstactCertificate import AbstractCertificate from abstractCertificate import AbstractCertificate
from certificate import Certificate from certificate import Certificate
from certificateSigningRequestSignature import CertificateSigningRequestSignature from certificateSigningRequestSignature import CertificateSigningRequestSignature
......
...@@ -2,7 +2,7 @@ from typing import Optional ...@@ -2,7 +2,7 @@ from typing import Optional
from signature import Signature from signature import Signature
from certificate import Certificate from certificate import Certificate
from abstactCertificate import AbstractCertificate from abstractCertificate import AbstractCertificate
class CertificateSigningRequestSignature(Signature): class CertificateSigningRequestSignature(Signature):
......
#!/usr/bin/env python3
#
# Copyright (C) 2023 Savoir-faire Linux Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Creates packaging targets for a distribution and architecture.
# This helps reduce the length of the top Makefile.
#
from typing import List
from OpenSSL import crypto
import requests
from certificate import Certificate
from abstractChain import AbstractChain
class Chain(AbstractChain):
def __init__(self, chain: List[Certificate], store: crypto.X509Store):
self._store = store
self._chain = chain
def verify(self) -> bool:
"""
Verify the chain.
:return: True if the chain is verified, False otherwise.
"""
for certificate in reversed(self._chain):
context = crypto.X509StoreContext(self._store, certificate.cert[1])
try:
if context.verify_certificate() is not None:
return False
self._add_trusted_cert(certificate)
except crypto.X509StoreContextError:
return False
return True
def _add_trusted_cert(self, cert: Certificate) -> None:
"""
Add a trusted certificate to the chain.
:param cert: The certificate to add.
:return: None
"""
certificate = cert.cert[1]
self._store.add_cert(certificate)
self._add_trusted_crl(cert)
def _add_trusted_crl(self, cert: Certificate) -> None:
"""
Add a trusted crl to the chain.
:param cert: The certificate to add.
:return: None
"""
self._store = Chain._add_trusted_crl(self._store, cert)
@staticmethod
def _add_trusted_crl(store: crypto.X509Store, cert: Certificate) -> crypto.X509Store:
"""
Add a trusted crl to the chain.
:param cert: The certificate to add.
:return: None
"""
certificate = cert.cert[1]
for extension_index in range(certificate.get_extension_count()):
if certificate.get_extension(extension_index).get_short_name() == b'crlDistributionPoints':
request = requests.get(
b'https://' + certificate.get_extension(extension_index).get_data().split(b'https://')[1]
)
if request.status_code != 200:
return store
store.add_crl(crypto.load_crl(crypto.FILETYPE_ASN1, request.content))
return store
return store
\ No newline at end of file
...@@ -21,11 +21,14 @@ ...@@ -21,11 +21,14 @@
# This helps reduce the length of the top Makefile. # This helps reduce the length of the top Makefile.
# #
from OpenSSL import crypto from OpenSSL import crypto
from zipfile import ZipFile
import re
from typing import List, Tuple from typing import List, Tuple
from jplManipulation import JPLStructure
from certificate import Certificate from certificate import Certificate
from pluginChain import PluginChain
from pluginSignature import PluginSignature from pluginSignature import PluginSignature
from utils import read_archive, save
class PluginCertificate(Certificate): class PluginCertificate(Certificate):
...@@ -49,6 +52,35 @@ class PluginCertificate(Certificate): ...@@ -49,6 +52,35 @@ class PluginCertificate(Certificate):
def create(subject: List[Tuple[str, str]], issuer: 'Certificate') -> 'PluginCertificate': def create(subject: List[Tuple[str, str]], issuer: 'Certificate') -> 'PluginCertificate':
return PluginCertificate(*Certificate.create(subject, issuer).cert, None) return PluginCertificate(*Certificate.create(subject, issuer).cert, None)
@staticmethod
def verify(plugin_path: str) -> bool:
"""
Verify a plugin.
:param plugin_path: The path to the plugin.
:return: True if the plugin is verified, False otherwise.
"""
cert = None
with ZipFile(plugin_path, 'r') as zip_file:
for filename in zip_file.namelist():
if filename.endswith('.crt'):
cert = zip_file.read(filename)
break
if cert is None:
return False
return PluginChain.load(cert).verify() and PluginSignature(None).verify_saving(plugin_path)
@staticmethod
def verify_from_archive(plugin_path: str) -> bool:
"""
Verify a plugin archived.
:param plugin_path: The path to the plugin.
:return: True if the plugin is verified, False otherwise.
"""
data = read_archive(plugin_path)
path_2_archive = path_2_archive[0:-4]
save(path_2_archive, data)
return PluginCertificate.verify(path_2_archive)
def sign(self, file: str) -> 'PluginSignature': def sign(self, file: str) -> 'PluginSignature':
""" """
Sign a plugin. Sign a plugin.
......
#!/usr/bin/env python3
#
# Copyright (C) 2023 Savoir-faire Linux Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Creates packaging targets for a distribution and architecture.
# This helps reduce the length of the top Makefile.
#
import re
from os import path
from typing import List
from OpenSSL import crypto
from utils import read
from chain import Chain
from certificate import Certificate
class PluginChain(Chain):
def __init__(self, chain: List[Certificate], store: crypto.X509Store) -> None:
super().__init__(chain, store)
@staticmethod
def load(content: bytes) -> 'PluginChain':
"""
Load a plugin certificate from a certificate and a plugin path.
:param content: The certificate chain to load.
:return: A PluginChain object.
"""
certificate_chain = re.split(b'-----END CERTIFICATE-----', content)[:-1]
store = crypto.X509Store()
# read the root certificate place on extra folder and add it to the store
plugin_store_root = Certificate(None, crypto.load_certificate(
crypto.FILETYPE_PEM, read(
path.join(
path.dirname(path.abspath(__file__)),
'../extras/resources/plugin-store-root.crt'
),
'rb'
)
),
None
)
store.add_cert(plugin_store_root.cert[1])
store = Chain._add_trusted_crl(store, plugin_store_root)
return PluginChain([
Certificate(
None,
crypto.load_certificate(
crypto.FILETYPE_PEM,
certificate + b'-----END CERTIFICATE-----\n'
),
None,
)
for certificate in certificate_chain
], store)
@staticmethod
def load_from_file(file_path: str) -> 'PluginChain':
"""Certificate
Load a plugin certificate from a plugin path.
:param file_path: The path to the plugin.
:return: A Chain object.
"""
return PluginChain.load(read(f'{file_path}.crt', 'rb'))
...@@ -22,11 +22,10 @@ ...@@ -22,11 +22,10 @@
# #
import os import os
import shutil import shutil
from msgpack import packb from msgpack import packb, loads
import re import re
from zipfile import ZipFile from zipfile import ZipFile
from typing import Optional from typing import Optional
from msgpack import loads
from OpenSSL import crypto from OpenSSL import crypto
...@@ -46,7 +45,6 @@ class PluginSignature(Signature): ...@@ -46,7 +45,6 @@ class PluginSignature(Signature):
""" """
super().__init__(cert) super().__init__(cert)
self.pluginPaths = None self.pluginPaths = None
self.__temp_dir = './plugin/'
def create(self, file: str) -> 'PluginSignature': def create(self, file: str) -> 'PluginSignature':
""" """
...@@ -94,7 +92,7 @@ class PluginSignature(Signature): ...@@ -94,7 +92,7 @@ class PluginSignature(Signature):
def verify_saving(self, path_2_file) -> bool: def verify_saving(self, path_2_file) -> bool:
""" """
Verify the plugin. Verify the plugin.
:param signatures: A dictionary containing the signatures of the plugin. :param path_2_file: the plugin path.
:return: True if the plugin is verified, False otherwise. :return: True if the plugin is verified, False otherwise.
""" """
if self.pluginPaths is None: if self.pluginPaths is None:
......
...@@ -26,7 +26,7 @@ from OpenSSL import crypto ...@@ -26,7 +26,7 @@ from OpenSSL import crypto
from utils import save, save_archive from utils import save, save_archive
from abstactCertificate import AbstractCertificate from abstractCertificate import AbstractCertificate
class Signature: class Signature:
""" """
......
-----BEGIN CERTIFICATE-----
MIIC6TCCAk6gAwIBAgIUN+/7SlZgFG9QunUKuKcCfa3iz6UwCgYIKoZIzj0EAwIw
gYUxITAfBgNVBAMMGEphbWkgRXh0ZW5zaW9uIFJvb3QgQy5BLjELMAkGA1UEBhMC
Q0ExDzANBgNVBAgMBlF1ZWJlYzERMA8GA1UEBwwITW9udHJlYWwxIDAeBgNVBAoM
F1Nhdm9pci1mYWlyZSBMaW51eCBJbmMuMQ0wCwYDVQQLDARKYW1pMCAXDTIzMDgy
MTE3MTIwOFoYDzIwNTMwODEzMTcxMjA4WjCBhTEhMB8GA1UEAwwYSmFtaSBFeHRl
bnNpb24gUm9vdCBDLkEuMQswCQYDVQQGEwJDQTEPMA0GA1UECAwGUXVlYmVjMREw
DwYDVQQHDAhNb250cmVhbDEgMB4GA1UECgwXU2F2b2lyLWZhaXJlIExpbnV4IElu
Yy4xDTALBgNVBAsMBEphbWkwgZswFAYHKoZIzj0CAQYJKyQDAwIIAQEOA4GCAAQT
e+kGNw4VevJKdlfykJ9fccpravn5aMqKnflIJ/cA8qzddiizqYVtemN77AGBJkJu
g5iVFKDKuwoL4kcsQ0y5U4kdwlYFirFDaCiKQclEWs5UbYcxYlcTEWcT7VKAZSIz
jUrSYzYM2tD0FKd7y1b88ldE8rzDvKZM8K68A/4uhKNVMFMwMgYDVR0fBCswKTAn
oCWgI4YhaHR0cHM6Ly9wbHVnaW5zLmphbWkubmV0L2NybC9yb290MB0GA1UdDgQW
BBQS+QqIe0nonOz+6V7b9GqF0P337jAKBggqhkjOPQQDAgOBiAAwgYQCQGrF82P+
/cJuuWajLCM6T9qUsnu+aCmwHNizPRydCVWasM0MgSeZYOzM7/5BHRU9PVJtctrG
hMBydvslOTo8Ib4CQArWTm9W4M9eTczuUif0gko8BCVsd2rEPOeebLXMIhqnHgSg
Nyya7X9SEB9AQMh1bGqXvrs4R67AvJLmSLwYi00=
-----END CERTIFICATE-----
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment