mirror of
https://github.com/NinjaCheetah/libWiiPy.git
synced 2025-04-27 22:01:01 -04:00
Added new CertificateChain class to manage the certs in a chain
Some checks failed
Build and publish documentation with Sphinx / build (push) Has been cancelled
Build and publish documentation with Sphinx / deploy (push) Has been cancelled
Some checks failed
Build and publish documentation with Sphinx / build (push) Has been cancelled
Build and publish documentation with Sphinx / deploy (push) Has been cancelled
This commit is contained in:
parent
2fdd808137
commit
3a44eaf2cf
@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
## Submodules
|
## Submodules
|
||||||
|
|
||||||
|
### libWiiPy.title.cert module
|
||||||
|
```{eval-rst}
|
||||||
|
.. automodule:: libWiiPy.title.cert
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
```
|
||||||
|
|
||||||
### libWiiPy.title.commonkeys module
|
### libWiiPy.title.commonkeys module
|
||||||
```{eval-rst}
|
```{eval-rst}
|
||||||
.. automodule:: libWiiPy.title.commonkeys
|
.. automodule:: libWiiPy.title.commonkeys
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import io
|
import io
|
||||||
from enum import IntEnum as _IntEnum
|
from enum import IntEnum as _IntEnum
|
||||||
from ..shared import _pad_bytes
|
from ..shared import _align_value, _pad_bytes
|
||||||
from .ticket import Ticket
|
from .ticket import Ticket
|
||||||
from .tmd import TMD
|
from .tmd import TMD
|
||||||
from Crypto.Hash import SHA1
|
from Crypto.Hash import SHA1
|
||||||
@ -123,6 +123,79 @@ class Certificate:
|
|||||||
return cert_data
|
return cert_data
|
||||||
|
|
||||||
|
|
||||||
|
class CertificateChain:
|
||||||
|
"""
|
||||||
|
A CertificateChain object used to parse the chain of certificates stored in a WAD that are used for the Wii's
|
||||||
|
content verification. The certificate chain is the format that the certificates are stored in as part of every WAD.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
ca_cert: Certificate
|
||||||
|
The CA certificate from the chain.
|
||||||
|
tmd_cert: Certificate
|
||||||
|
The CP (TMD) certificate from the chain.
|
||||||
|
ticket_cert: Certificate
|
||||||
|
The XS (Ticket) certificate from the chain.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.ca_cert: Certificate = Certificate()
|
||||||
|
self.tmd_cert: Certificate = Certificate()
|
||||||
|
self.ticket_cert: Certificate = Certificate()
|
||||||
|
|
||||||
|
def load(self, cert_chain: bytes) -> None:
|
||||||
|
"""
|
||||||
|
Loads certificate chain data into the CertificateChain object, allowing you to parse the individual
|
||||||
|
certificates stored in the chain.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
cert_chain: bytes
|
||||||
|
The data for the certificate chain to load.
|
||||||
|
"""
|
||||||
|
with (io.BytesIO(cert_chain) as cert_chain_data):
|
||||||
|
# Read the two fields that denote different length sections of the certificate, so that we know how long
|
||||||
|
# this certificate is in total.
|
||||||
|
offset = 0x0
|
||||||
|
for _ in range(3):
|
||||||
|
cert_chain_data.seek(offset)
|
||||||
|
cert_type = CertificateType.from_bytes(cert_chain_data.read(0x4))
|
||||||
|
cert_chain_data.seek(offset + 0x80 + CertificateSignatureLength[cert_type.name].value)
|
||||||
|
key_type = CertificateKeyType.from_bytes(cert_chain_data.read(0x4))
|
||||||
|
cert_size = _align_value(0xC8 + CertificateSignatureLength[cert_type.name].value +
|
||||||
|
CertificateKeyLength[key_type.name].value)
|
||||||
|
cert_chain_data.seek(offset + 0x0)
|
||||||
|
cert = Certificate()
|
||||||
|
cert.load(cert_chain_data.read(cert_size))
|
||||||
|
if cert.issuer == "Root":
|
||||||
|
self.ca_cert = cert
|
||||||
|
elif cert.issuer.find("Root-CA") != -1:
|
||||||
|
if cert.child_name.find("CP") != -1:
|
||||||
|
self.tmd_cert = cert
|
||||||
|
elif cert.child_name.find("XS") != -1:
|
||||||
|
self.ticket_cert = cert
|
||||||
|
else:
|
||||||
|
raise ValueError("Unknown certificate in chain!")
|
||||||
|
else:
|
||||||
|
raise ValueError("Unknown certificate in chain!")
|
||||||
|
offset += cert_size
|
||||||
|
|
||||||
|
def dump(self) -> bytes:
|
||||||
|
"""
|
||||||
|
Dumps the full certificate chain back into bytes. This chain will always be formatted with the CA cert first,
|
||||||
|
followed by the CP (TMD) cert, then finally the XS (Ticket) cert.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bytes
|
||||||
|
The full certificate chain as bytes.
|
||||||
|
"""
|
||||||
|
cert_chain_data = b''
|
||||||
|
cert_chain_data += self.ca_cert.dump()
|
||||||
|
cert_chain_data += self.tmd_cert.dump()
|
||||||
|
cert_chain_data += self.ticket_cert.dump()
|
||||||
|
return cert_chain_data
|
||||||
|
|
||||||
|
|
||||||
def verify_ca_cert(ca_cert: Certificate) -> bool:
|
def verify_ca_cert(ca_cert: Certificate) -> bool:
|
||||||
"""
|
"""
|
||||||
Verify a Wii CA certificate using the root public key.
|
Verify a Wii CA certificate using the root public key.
|
||||||
@ -180,7 +253,7 @@ def verify_cert_sig(ca_cert: Certificate, target_cert: Certificate) -> bool:
|
|||||||
bool
|
bool
|
||||||
Whether the certificate's signature is valid or not.
|
Whether the certificate's signature is valid or not.
|
||||||
"""
|
"""
|
||||||
cert_hash = SHA1.new(target_cert.dump()[576:])
|
cert_hash = SHA1.new(target_cert.dump()[320:])
|
||||||
public_key = RSA.construct((ca_cert.pub_key_modulus, ca_cert.pub_key_exponent))
|
public_key = RSA.construct((ca_cert.pub_key_modulus, ca_cert.pub_key_exponent))
|
||||||
try:
|
try:
|
||||||
pkcs1_15.new(public_key).verify(cert_hash, target_cert.signature)
|
pkcs1_15.new(public_key).verify(cert_hash, target_cert.signature)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user