laforge submitted this change.

View Change

Approvals: Jenkins Builder: Verified laforge: Looks good to me, approved
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(-)

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

To view, visit change 40526. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: merged
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: Ic678e6c4a4c1a01de87a8dce26f4a5e452e8562a
Gerrit-Change-Number: 40526
Gerrit-PatchSet: 7
Gerrit-Owner: Hoernchen <ewild@sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge@osmocom.org>