laforge has submitted this change. ( https://gerrit.osmocom.org/c/pysim/+/40526?usp=email )
Change subject: smdpp: fix asn1tool OBJECT IDENTIFIER decoding ......................................................................
smdpp: fix asn1tool OBJECT IDENTIFIER decoding
While at it make the linter happy. The feature to ignore blocks is making slow progress: https://github.com/astral-sh/ruff/issues/3711#
Change-Id: Ic678e6c4a4c1a01de87a8dce26f4a5e452e8562a --- M osmo-smdpp.py 1 file changed, 113 insertions(+), 29 deletions(-)
Approvals: Jenkins Builder: Verified laforge: Looks good to me, approved
diff --git a/osmo-smdpp.py b/osmo-smdpp.py index ea45917..27923d4 100755 --- a/osmo-smdpp.py +++ b/osmo-smdpp.py @@ -17,37 +17,123 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see http://www.gnu.org/licenses/.
-from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature -from cryptography import x509 -from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec, dh -from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat, PrivateFormat, NoEncryption, ParameterFormat -from pathlib import Path -import json -import sys -import argparse -import uuid -import os -import functools -from typing import Optional, Dict, List -from pprint import pprint as pp - -import base64 -from base64 import b64decode -from klein import Klein -from twisted.web.iweb import IRequest +# asn1tools issue https://github.com/eerimoq/asn1tools/issues/194 +# must be first here import asn1tools +import asn1tools.codecs.ber +import asn1tools.codecs.der +# do not move the code +def fix_asn1_oid_decoding(): + fix_asn1_schema = """ + TestModule DEFINITIONS ::= BEGIN + TestOid ::= SEQUENCE { + oid OBJECT IDENTIFIER + } + END + """
-from osmocom.utils import h2b, b2h, swap_nibbles + fix_asn1_asn1 = asn1tools.compile_string(fix_asn1_schema, codec='der') + fix_asn1_oid_string = '2.999.10' + fix_asn1_encoded = fix_asn1_asn1.encode('TestOid', {'oid': fix_asn1_oid_string}) + fix_asn1_decoded = fix_asn1_asn1.decode('TestOid', fix_asn1_encoded)
-import pySim.esim.rsp as rsp -from pySim.esim import saip, PMO -from pySim.esim.es8p import * -from pySim.esim.x509_cert import oid, cert_policy_has_oid, cert_get_auth_key_id -from pySim.esim.x509_cert import CertAndPrivkey, CertificateSet, cert_get_subject_key_id, VerifyError + if (fix_asn1_decoded['oid'] != fix_asn1_oid_string): + # ASN.1 OBJECT IDENTIFIER Decoding Issue: + # + # In ASN.1 BER/DER encoding, the first two arcs of an OBJECT IDENTIFIER are + # combined into a single value: (40 * arc0) + arc1. This is encoded as a base-128 + # variable-length quantity (and commonly known as VLQ or base-128 encoding) + # as specified in ITU-T X.690 §8.19, it can span multiple bytes if + # the value is large. + # + # For arc0 = 0 or 1, arc1 must be in [0, 39]. For arc0 = 2, arc1 can be any non-negative integer. + # All subsequent arcs (arc2, arc3, ...) are each encoded as a separate base-128 VLQ. + # + # The decoding bug occurs when the decoder does not properly split the first + # subidentifier for arc0 = 2 and arc1 >= 40. Instead of decoding: + # - arc0 = 2 + # - arc1 = (first_subidentifier - 80) + # it may incorrectly interpret the first_subidentifier as arc0 = (first_subidentifier // 40), + # arc1 = (first_subidentifier % 40), which is only valid for arc1 < 40. + # + # This patch handles it properly for all valid OBJECT IDENTIFIERs + # with large second arcs, by applying the ASN.1 rules: + # - if first_subidentifier < 40: arc0 = 0, arc1 = first_subidentifier + # - elif first_subidentifier < 80: arc0 = 1, arc1 = first_subidentifier - 40 + # - else: arc0 = 2, arc1 = first_subidentifier - 80 + # + # This problem is not uncommon, see for example https://github.com/randombit/botan/issues/4023
-import logging + def fixed_decode_object_identifier(data, offset, end_offset): + """Decode ASN.1 OBJECT IDENTIFIER from bytes to dotted string, fixing large second arc handling.""" + def read_subidentifier(data, offset): + value = 0 + while True: + b = data[offset] + value = (value << 7) | (b & 0x7F) + offset += 1 + if not (b & 0x80): + break + return value, offset + + subid, offset = read_subidentifier(data, offset) + if subid < 40: + first = 0 + second = subid + elif subid < 80: + first = 1 + second = subid - 40 + else: + first = 2 + second = subid - 80 + arcs = [first, second] + + while offset < end_offset: + subid, offset = read_subidentifier(data, offset) + arcs.append(subid) + + return '.'.join(str(x) for x in arcs) + + asn1tools.codecs.ber.decode_object_identifier = fixed_decode_object_identifier + asn1tools.codecs.der.decode_object_identifier = fixed_decode_object_identifier + + # test our patch + asn1 = asn1tools.compile_string(fix_asn1_schema, codec='der') + decoded = asn1.decode('TestOid', fix_asn1_encoded)['oid'] + assert fix_asn1_oid_string == str(decoded) + +fix_asn1_oid_decoding() + +from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature # noqa: E402 +from cryptography import x509 # noqa: E402 +from cryptography.exceptions import InvalidSignature # noqa: E402 +from cryptography.hazmat.primitives import hashes # noqa: E402 +from cryptography.hazmat.primitives.asymmetric import ec, dh # noqa: E402 +from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat, PrivateFormat, NoEncryption, ParameterFormat # noqa: E402 +from pathlib import Path # noqa: E402 +import json # noqa: E402 +import sys # noqa: E402 +import argparse # noqa: E402 +import uuid # noqa: E402 +import os # noqa: E402 +import functools # noqa: E402 +from typing import Optional, Dict, List # noqa: E402 +from pprint import pprint as pp # noqa: E402 + +import base64 # noqa: E402 +from base64 import b64decode # noqa: E402 +from klein import Klein # noqa: E402 +from twisted.web.iweb import IRequest # noqa: E402 + +from osmocom.utils import h2b, b2h, swap_nibbles # noqa: E402 + +import pySim.esim.rsp as rsp # noqa: E402 +from pySim.esim import saip, PMO # noqa: E402 +from pySim.esim.es8p import ProfileMetadata,UnprotectedProfilePackage,ProtectedProfilePackage,BoundProfilePackage,BspInstance # noqa: E402 +from pySim.esim.x509_cert import oid, cert_policy_has_oid, cert_get_auth_key_id # noqa: E402 +from pySim.esim.x509_cert import CertAndPrivkey, CertificateSet, cert_get_subject_key_id, VerifyError # noqa: E402 + +import logging # noqa: E402 logger = logging.getLogger(__name__)
# HACK: make this configurable @@ -138,8 +224,6 @@
if isinstance(ext_der, bytes): try: - import asn1tools - permitted_eins_schema = """ PermittedEins DEFINITIONS ::= BEGIN PermittedEins ::= SEQUENCE OF PrintableString