适配腾讯云函数
This commit is contained in:
commit
3f15fd8504
142
index.py
Normal file
142
index.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
import rsa
|
||||||
|
import time
|
||||||
|
import logging as log
|
||||||
|
|
||||||
|
log.basicConfig(level=log.INFO)
|
||||||
|
BI_RM = list("0123456789abcdefghijklmnopqrstuvwxyz")
|
||||||
|
b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||||
|
|
||||||
|
def main(username, password):
|
||||||
|
s = requests.Session()
|
||||||
|
login(s,username, password)
|
||||||
|
rand = str(round(time.time() * 1000))
|
||||||
|
surl = f'https://api.cloud.189.cn/mkt/userSign.action?rand={rand}&clientType=TELEANDROID&version=8.6.3&model=SM-G930K'
|
||||||
|
url = f'https://m.cloud.189.cn/v2/drawPrizeMarketDetails.action?taskId=TASK_SIGNIN&activityId=ACT_SIGNIN'
|
||||||
|
url2 = f'https://m.cloud.189.cn/v2/drawPrizeMarketDetails.action?taskId=TASK_SIGNIN_PHOTOS&activityId=ACT_SIGNIN'
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Linux; Android 5.1.1; SM-G930K Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.136 Mobile Safari/537.36 Ecloud/8.6.3 Android/22 clientId/355325117317828 clientModel/SM-G930K imsi/460071114317824 clientChannelId/qq proVersion/1.0.6',
|
||||||
|
"Referer": "https://m.cloud.189.cn/zhuanti/2016/sign/index.jsp?albumBackupOpened=1",
|
||||||
|
"Host": "m.cloud.189.cn",
|
||||||
|
"Accept-Encoding": "gzip, deflate",
|
||||||
|
}
|
||||||
|
response = s.get(surl, headers=headers)
|
||||||
|
netdiskBonus = response.json()['netdiskBonus']
|
||||||
|
if (response.json()['isSign'] == "false"):
|
||||||
|
log.info(f"未签到,签到获得{netdiskBonus}M空间")
|
||||||
|
else:
|
||||||
|
log.info(f"已经签到过了,签到获得{netdiskBonus}M空间")
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Linux; Android 5.1.1; SM-G930K Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.136 Mobile Safari/537.36 Ecloud/8.6.3 Android/22 clientId/355325117317828 clientModel/SM-G930K imsi/460071114317824 clientChannelId/qq proVersion/1.0.6',
|
||||||
|
"Referer": "https://m.cloud.189.cn/zhuanti/2016/sign/index.jsp?albumBackupOpened=1",
|
||||||
|
"Host": "m.cloud.189.cn",
|
||||||
|
"Accept-Encoding": "gzip, deflate",
|
||||||
|
}
|
||||||
|
response = s.get(url, headers=headers)
|
||||||
|
if ("errorCode" in response.text):
|
||||||
|
log.info(response.text)
|
||||||
|
else:
|
||||||
|
description = response.json()['description']
|
||||||
|
log.info(f"抽奖获得{description}")
|
||||||
|
response = s.get(url2, headers=headers)
|
||||||
|
if ("errorCode" in response.text):
|
||||||
|
log.info(response.text)
|
||||||
|
else:
|
||||||
|
description = response.json()['description']
|
||||||
|
log.info(f"抽奖获得{description}")
|
||||||
|
|
||||||
|
|
||||||
|
def int2char(a):
|
||||||
|
return BI_RM[a]
|
||||||
|
|
||||||
|
|
||||||
|
def b64tohex(a):
|
||||||
|
d = ""
|
||||||
|
e = 0
|
||||||
|
c = 0
|
||||||
|
for i in range(len(a)):
|
||||||
|
if list(a)[i] != "=":
|
||||||
|
v = b64map.index(list(a)[i])
|
||||||
|
if 0 == e:
|
||||||
|
e = 1
|
||||||
|
d += int2char(v >> 2)
|
||||||
|
c = 3 & v
|
||||||
|
elif 1 == e:
|
||||||
|
e = 2
|
||||||
|
d += int2char(c << 2 | v >> 4)
|
||||||
|
c = 15 & v
|
||||||
|
elif 2 == e:
|
||||||
|
e = 3
|
||||||
|
d += int2char(c)
|
||||||
|
d += int2char(v >> 2)
|
||||||
|
c = 3 & v
|
||||||
|
else:
|
||||||
|
e = 0
|
||||||
|
d += int2char(c << 2 | v >> 4)
|
||||||
|
d += int2char(15 & v)
|
||||||
|
if e == 1:
|
||||||
|
d += int2char(c << 2)
|
||||||
|
return d
|
||||||
|
|
||||||
|
def rsa_encode(j_rsakey, string):
|
||||||
|
rsa_key = f"-----BEGIN PUBLIC KEY-----\n{j_rsakey}\n-----END PUBLIC KEY-----"
|
||||||
|
pubkey = rsa.PublicKey.load_pkcs1_openssl_pem(rsa_key.encode())
|
||||||
|
result = b64tohex((base64.b64encode(rsa.encrypt(f'{string}'.encode(), pubkey))).decode())
|
||||||
|
return result
|
||||||
|
|
||||||
|
def calculate_md5_sign(params):
|
||||||
|
return hashlib.md5('&'.join(sorted(params.split('&'))).encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
def login(s,username, password):
|
||||||
|
url = "https://cloud.189.cn/udb/udb_login.jsp?pageId=1&redirectURL=/main.action"
|
||||||
|
r = s.get(url)
|
||||||
|
captchaToken = re.findall(r"captchaToken' value='(.+?)'", r.text)[0]
|
||||||
|
lt = re.findall(r'lt = "(.+?)"', r.text)[0]
|
||||||
|
returnUrl = re.findall(r"returnUrl = '(.+?)'", r.text)[0]
|
||||||
|
paramId = re.findall(r'paramId = "(.+?)"', r.text)[0]
|
||||||
|
j_rsakey = re.findall(r'j_rsaKey" value="(\S+)"', r.text, re.M)[0]
|
||||||
|
s.headers.update({"lt": lt})
|
||||||
|
|
||||||
|
username = rsa_encode(j_rsakey, username)
|
||||||
|
password = rsa_encode(j_rsakey, password)
|
||||||
|
url = "https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do"
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/76.0',
|
||||||
|
'Referer': 'https://open.e.189.cn/',
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"appKey": "cloud",
|
||||||
|
"accountType": '01',
|
||||||
|
"userName": f"{{RSA}}{username}",
|
||||||
|
"password": f"{{RSA}}{password}",
|
||||||
|
"validateCode": "",
|
||||||
|
"captchaToken": captchaToken,
|
||||||
|
"returnUrl": returnUrl,
|
||||||
|
"mailSuffix": "@189.cn",
|
||||||
|
"paramId": paramId
|
||||||
|
}
|
||||||
|
r = s.post(url, data=data, headers=headers, timeout=5)
|
||||||
|
if (r.json()['result'] == 0):
|
||||||
|
log.info(r.json()['msg'])
|
||||||
|
else:
|
||||||
|
log.info(r.json()['msg'])
|
||||||
|
redirect_url = r.json()['toUrl']
|
||||||
|
r = s.get(redirect_url)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def main_handler(event, context):
|
||||||
|
i = 1
|
||||||
|
user = [
|
||||||
|
{'user': '152xxxxxxxx', 'pwd': 'xxxxxxxx'}
|
||||||
|
]
|
||||||
|
for u in user:
|
||||||
|
log.info("第%s个帐号"%i)
|
||||||
|
i += 1
|
||||||
|
try:
|
||||||
|
main(u['user'],u['pwd'])
|
||||||
|
except Exception as result:
|
||||||
|
log.error("异常%s"%result)
|
||||||
|
|
||||||
1
pyasn1-0.4.8.dist-info/INSTALLER
Normal file
1
pyasn1-0.4.8.dist-info/INSTALLER
Normal file
@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
24
pyasn1-0.4.8.dist-info/LICENSE.rst
Normal file
24
pyasn1-0.4.8.dist-info/LICENSE.rst
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
38
pyasn1-0.4.8.dist-info/METADATA
Normal file
38
pyasn1-0.4.8.dist-info/METADATA
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: pyasn1
|
||||||
|
Version: 0.4.8
|
||||||
|
Summary: ASN.1 types and codecs
|
||||||
|
Home-page: https://github.com/etingof/pyasn1
|
||||||
|
Author: Ilya Etingof
|
||||||
|
Author-email: etingof@gmail.com
|
||||||
|
Maintainer: Ilya Etingof <etingof@gmail.com>
|
||||||
|
License: BSD
|
||||||
|
Platform: any
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Environment :: Console
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: Intended Audience :: Education
|
||||||
|
Classifier: Intended Audience :: Information Technology
|
||||||
|
Classifier: Intended Audience :: System Administrators
|
||||||
|
Classifier: Intended Audience :: Telecommunications Industry
|
||||||
|
Classifier: License :: OSI Approved :: BSD License
|
||||||
|
Classifier: Natural Language :: English
|
||||||
|
Classifier: Operating System :: OS Independent
|
||||||
|
Classifier: Programming Language :: Python :: 2
|
||||||
|
Classifier: Programming Language :: Python :: 2.4
|
||||||
|
Classifier: Programming Language :: Python :: 2.5
|
||||||
|
Classifier: Programming Language :: Python :: 2.6
|
||||||
|
Classifier: Programming Language :: Python :: 2.7
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python :: 3.2
|
||||||
|
Classifier: Programming Language :: Python :: 3.3
|
||||||
|
Classifier: Programming Language :: Python :: 3.4
|
||||||
|
Classifier: Programming Language :: Python :: 3.5
|
||||||
|
Classifier: Programming Language :: Python :: 3.6
|
||||||
|
Classifier: Programming Language :: Python :: 3.7
|
||||||
|
Classifier: Topic :: Communications
|
||||||
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||||
|
|
||||||
|
Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)
|
||||||
|
|
||||||
|
|
||||||
79
pyasn1-0.4.8.dist-info/RECORD
Normal file
79
pyasn1-0.4.8.dist-info/RECORD
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
pyasn1-0.4.8.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
pyasn1-0.4.8.dist-info/LICENSE.rst,sha256=IsXMaSKrXWn7oy2MXuTN0UmBUIy1OvwOvYVZOEf9laU,1334
|
||||||
|
pyasn1-0.4.8.dist-info/METADATA,sha256=Mx_DbLo2GA_t9nOIsqu-18vjHdTjMR1LtUzdcfLzE0Y,1521
|
||||||
|
pyasn1-0.4.8.dist-info/RECORD,,
|
||||||
|
pyasn1-0.4.8.dist-info/WHEEL,sha256=8zNYZbwQSXoB9IfXOjPfeNwvAsALAjffgk27FqvCWbo,110
|
||||||
|
pyasn1-0.4.8.dist-info/top_level.txt,sha256=dnNEQt3nIDIO5mSCCOB5obQHrjDOUsRycdBujc2vrWE,7
|
||||||
|
pyasn1-0.4.8.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
||||||
|
pyasn1/__init__.py,sha256=1Rn8wrJioqfDz7ORFwMehoT15xHOVeiiQD5pZW37D8s,175
|
||||||
|
pyasn1/__pycache__/__init__.cpython-39.pyc,,
|
||||||
|
pyasn1/__pycache__/debug.cpython-39.pyc,,
|
||||||
|
pyasn1/__pycache__/error.cpython-39.pyc,,
|
||||||
|
pyasn1/codec/__init__.py,sha256=EEDlJYS172EH39GUidN_8FbkNcWY9OVV8e30AV58pn0,59
|
||||||
|
pyasn1/codec/__pycache__/__init__.cpython-39.pyc,,
|
||||||
|
pyasn1/codec/ber/__init__.py,sha256=EEDlJYS172EH39GUidN_8FbkNcWY9OVV8e30AV58pn0,59
|
||||||
|
pyasn1/codec/ber/__pycache__/__init__.cpython-39.pyc,,
|
||||||
|
pyasn1/codec/ber/__pycache__/decoder.cpython-39.pyc,,
|
||||||
|
pyasn1/codec/ber/__pycache__/encoder.cpython-39.pyc,,
|
||||||
|
pyasn1/codec/ber/__pycache__/eoo.cpython-39.pyc,,
|
||||||
|
pyasn1/codec/ber/decoder.py,sha256=7-WINr38zVEa3KUkmshh8FjK6QnFaA8Y7j7XaTgYfRk,59708
|
||||||
|
pyasn1/codec/ber/encoder.py,sha256=xHl01PCIAiHZXev4x01sjbCgAUKcsTT6SzaLI3nt-9E,27741
|
||||||
|
pyasn1/codec/ber/eoo.py,sha256=eZ6lEyHdayMcMmNqtceDIyzf7u5lOeZoRK-WEUxVThI,626
|
||||||
|
pyasn1/codec/cer/__init__.py,sha256=EEDlJYS172EH39GUidN_8FbkNcWY9OVV8e30AV58pn0,59
|
||||||
|
pyasn1/codec/cer/__pycache__/__init__.cpython-39.pyc,,
|
||||||
|
pyasn1/codec/cer/__pycache__/decoder.cpython-39.pyc,,
|
||||||
|
pyasn1/codec/cer/__pycache__/encoder.cpython-39.pyc,,
|
||||||
|
pyasn1/codec/cer/decoder.py,sha256=ZYBqtDGNiYmKDpKDvioMDf-TYVWoJeZY3I8TEAKuk5s,3745
|
||||||
|
pyasn1/codec/cer/encoder.py,sha256=PGtzcIelIHj5d5Yqc5FATMEIWCJybQYFlCaK1gy-NIA,9409
|
||||||
|
pyasn1/codec/der/__init__.py,sha256=EEDlJYS172EH39GUidN_8FbkNcWY9OVV8e30AV58pn0,59
|
||||||
|
pyasn1/codec/der/__pycache__/__init__.cpython-39.pyc,,
|
||||||
|
pyasn1/codec/der/__pycache__/decoder.cpython-39.pyc,,
|
||||||
|
pyasn1/codec/der/__pycache__/encoder.cpython-39.pyc,,
|
||||||
|
pyasn1/codec/der/decoder.py,sha256=kinXcogMDPGlR3f7hmAxRv2YbQyeP-UhuKM0r8gkbeA,2722
|
||||||
|
pyasn1/codec/der/encoder.py,sha256=ZfRRxSCefQyLg0DLNb4zllaYf5_AWGIv3SPzB83Ln2I,3073
|
||||||
|
pyasn1/codec/native/__init__.py,sha256=EEDlJYS172EH39GUidN_8FbkNcWY9OVV8e30AV58pn0,59
|
||||||
|
pyasn1/codec/native/__pycache__/__init__.cpython-39.pyc,,
|
||||||
|
pyasn1/codec/native/__pycache__/decoder.cpython-39.pyc,,
|
||||||
|
pyasn1/codec/native/__pycache__/encoder.cpython-39.pyc,,
|
||||||
|
pyasn1/codec/native/decoder.py,sha256=4Q29tdKyytK3Oz-m94MSWxxPi_GhcBKvUfvPNKQcL0Y,7671
|
||||||
|
pyasn1/codec/native/encoder.py,sha256=0eMLWR49dwMA1X4si0XswR1kX1aDAWyCeUNTpEbChag,8002
|
||||||
|
pyasn1/compat/__init__.py,sha256=EEDlJYS172EH39GUidN_8FbkNcWY9OVV8e30AV58pn0,59
|
||||||
|
pyasn1/compat/__pycache__/__init__.cpython-39.pyc,,
|
||||||
|
pyasn1/compat/__pycache__/binary.cpython-39.pyc,,
|
||||||
|
pyasn1/compat/__pycache__/calling.cpython-39.pyc,,
|
||||||
|
pyasn1/compat/__pycache__/dateandtime.cpython-39.pyc,,
|
||||||
|
pyasn1/compat/__pycache__/integer.cpython-39.pyc,,
|
||||||
|
pyasn1/compat/__pycache__/octets.cpython-39.pyc,,
|
||||||
|
pyasn1/compat/__pycache__/string.cpython-39.pyc,,
|
||||||
|
pyasn1/compat/binary.py,sha256=mgWqHmr_SMEdB2WVVr6jyYMnodSbPP6IByE5qKccWLM,698
|
||||||
|
pyasn1/compat/calling.py,sha256=uTk3nJtGrElqJi8t34SoO8-eWFBG0gwNhXrlo1YmFEE,379
|
||||||
|
pyasn1/compat/dateandtime.py,sha256=zHvXXBp4t3XJ6teg_tz6qgNDevzd93qnrLoEbNxZQ_E,482
|
||||||
|
pyasn1/compat/integer.py,sha256=k6tqyxXMC0zJoU-Rz4oUPPoUpTmWXE6Prnzu0tkmmks,2988
|
||||||
|
pyasn1/compat/octets.py,sha256=ICe-DVLBIOHmNSz-sp3ioMh--smodJ4VW3Ju0ogJMWA,1359
|
||||||
|
pyasn1/compat/string.py,sha256=exqXJmPM6vYj4MjzsjciQdpUcJprRdgrLma8I4UcYHA,505
|
||||||
|
pyasn1/debug.py,sha256=HWGbLlEPLoCNyHqBd1Vd_KK91TppEn3CA4YgUxktT2k,3726
|
||||||
|
pyasn1/error.py,sha256=DIn2FWY3ACYNbk_42b3ny2bevkehpK2lOqfAsfdkvBE,2257
|
||||||
|
pyasn1/type/__init__.py,sha256=EEDlJYS172EH39GUidN_8FbkNcWY9OVV8e30AV58pn0,59
|
||||||
|
pyasn1/type/__pycache__/__init__.cpython-39.pyc,,
|
||||||
|
pyasn1/type/__pycache__/base.cpython-39.pyc,,
|
||||||
|
pyasn1/type/__pycache__/char.cpython-39.pyc,,
|
||||||
|
pyasn1/type/__pycache__/constraint.cpython-39.pyc,,
|
||||||
|
pyasn1/type/__pycache__/error.cpython-39.pyc,,
|
||||||
|
pyasn1/type/__pycache__/namedtype.cpython-39.pyc,,
|
||||||
|
pyasn1/type/__pycache__/namedval.cpython-39.pyc,,
|
||||||
|
pyasn1/type/__pycache__/opentype.cpython-39.pyc,,
|
||||||
|
pyasn1/type/__pycache__/tag.cpython-39.pyc,,
|
||||||
|
pyasn1/type/__pycache__/tagmap.cpython-39.pyc,,
|
||||||
|
pyasn1/type/__pycache__/univ.cpython-39.pyc,,
|
||||||
|
pyasn1/type/__pycache__/useful.cpython-39.pyc,,
|
||||||
|
pyasn1/type/base.py,sha256=TX7qdOX3EPiY7-11MY4fwK2Hy6nQsrdQ_M41aUcApno,22386
|
||||||
|
pyasn1/type/char.py,sha256=5HH8r1IqZMDCsfDlQHVCRphLlFuZ93bE2NW78CgeUTI,11397
|
||||||
|
pyasn1/type/constraint.py,sha256=0Qsth_0JctnDMvOSe5R-vd9IosgjqkKZT_X9lBRXtuI,22132
|
||||||
|
pyasn1/type/error.py,sha256=4_BHdjX-AL5WMTpU-tX1Nfo_P88c2z1sDvqPU-S9Bns,246
|
||||||
|
pyasn1/type/namedtype.py,sha256=VIL3H3oPgA0zNrDSeAhKmi4CZGTb69uDBVNJzzRk3wM,16368
|
||||||
|
pyasn1/type/namedval.py,sha256=dXYWiVTihvBy4RiebGY3AlIXsJvW78mJ1L7JSw-H7Qw,4886
|
||||||
|
pyasn1/type/opentype.py,sha256=pUpnPqv8o4AFeIsmGHDTFfuxXAq7FvG3hrTEnoAgBO8,2848
|
||||||
|
pyasn1/type/tag.py,sha256=nAK54C0_F_DL4_IaWRthIfIYBOTuXZoVVcbcbqgZiVA,9486
|
||||||
|
pyasn1/type/tagmap.py,sha256=2bwm0hqxG2gvXYheOI_iasfl2Z_B93qU7y39EHteUvs,2998
|
||||||
|
pyasn1/type/univ.py,sha256=FXc_VOStZfC-xIVTznpFO0qTq1aO4XyJFU0ayQWgPMY,108921
|
||||||
|
pyasn1/type/useful.py,sha256=r_K6UhgcrJ0ej658X-s9522I9T7oYVdmEKcbXTkZMds,5368
|
||||||
6
pyasn1-0.4.8.dist-info/WHEEL
Normal file
6
pyasn1-0.4.8.dist-info/WHEEL
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: bdist_wheel (0.33.6)
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py2-none-any
|
||||||
|
Tag: py3-none-any
|
||||||
|
|
||||||
1
pyasn1-0.4.8.dist-info/top_level.txt
Normal file
1
pyasn1-0.4.8.dist-info/top_level.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
pyasn1
|
||||||
1
pyasn1-0.4.8.dist-info/zip-safe
Normal file
1
pyasn1-0.4.8.dist-info/zip-safe
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
7
pyasn1/__init__.py
Normal file
7
pyasn1/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
# https://www.python.org/dev/peps/pep-0396/
|
||||||
|
__version__ = '0.4.8'
|
||||||
|
|
||||||
|
if sys.version_info[:2] < (2, 4):
|
||||||
|
raise RuntimeError('PyASN1 requires Python 2.4 or later')
|
||||||
1
pyasn1/codec/__init__.py
Normal file
1
pyasn1/codec/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# This file is necessary to make this directory a package.
|
||||||
1
pyasn1/codec/ber/__init__.py
Normal file
1
pyasn1/codec/ber/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# This file is necessary to make this directory a package.
|
||||||
1682
pyasn1/codec/ber/decoder.py
Normal file
1682
pyasn1/codec/ber/decoder.py
Normal file
File diff suppressed because it is too large
Load Diff
890
pyasn1/codec/ber/encoder.py
Normal file
890
pyasn1/codec/ber/encoder.py
Normal file
@ -0,0 +1,890 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from pyasn1 import debug
|
||||||
|
from pyasn1 import error
|
||||||
|
from pyasn1.codec.ber import eoo
|
||||||
|
from pyasn1.compat.integer import to_bytes
|
||||||
|
from pyasn1.compat.octets import (int2oct, oct2int, ints2octs, null,
|
||||||
|
str2octs, isOctetsType)
|
||||||
|
from pyasn1.type import char
|
||||||
|
from pyasn1.type import tag
|
||||||
|
from pyasn1.type import univ
|
||||||
|
from pyasn1.type import useful
|
||||||
|
|
||||||
|
__all__ = ['encode']
|
||||||
|
|
||||||
|
LOG = debug.registerLoggee(__name__, flags=debug.DEBUG_ENCODER)
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractItemEncoder(object):
|
||||||
|
supportIndefLenMode = True
|
||||||
|
|
||||||
|
# An outcome of otherwise legit call `encodeFun(eoo.endOfOctets)`
|
||||||
|
eooIntegerSubstrate = (0, 0)
|
||||||
|
eooOctetsSubstrate = ints2octs(eooIntegerSubstrate)
|
||||||
|
|
||||||
|
# noinspection PyMethodMayBeStatic
|
||||||
|
def encodeTag(self, singleTag, isConstructed):
|
||||||
|
tagClass, tagFormat, tagId = singleTag
|
||||||
|
encodedTag = tagClass | tagFormat
|
||||||
|
if isConstructed:
|
||||||
|
encodedTag |= tag.tagFormatConstructed
|
||||||
|
|
||||||
|
if tagId < 31:
|
||||||
|
return encodedTag | tagId,
|
||||||
|
|
||||||
|
else:
|
||||||
|
substrate = tagId & 0x7f,
|
||||||
|
|
||||||
|
tagId >>= 7
|
||||||
|
|
||||||
|
while tagId:
|
||||||
|
substrate = (0x80 | (tagId & 0x7f),) + substrate
|
||||||
|
tagId >>= 7
|
||||||
|
|
||||||
|
return (encodedTag | 0x1F,) + substrate
|
||||||
|
|
||||||
|
def encodeLength(self, length, defMode):
|
||||||
|
if not defMode and self.supportIndefLenMode:
|
||||||
|
return (0x80,)
|
||||||
|
|
||||||
|
if length < 0x80:
|
||||||
|
return length,
|
||||||
|
|
||||||
|
else:
|
||||||
|
substrate = ()
|
||||||
|
while length:
|
||||||
|
substrate = (length & 0xff,) + substrate
|
||||||
|
length >>= 8
|
||||||
|
|
||||||
|
substrateLen = len(substrate)
|
||||||
|
|
||||||
|
if substrateLen > 126:
|
||||||
|
raise error.PyAsn1Error('Length octets overflow (%d)' % substrateLen)
|
||||||
|
|
||||||
|
return (0x80 | substrateLen,) + substrate
|
||||||
|
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
raise error.PyAsn1Error('Not implemented')
|
||||||
|
|
||||||
|
def encode(self, value, asn1Spec=None, encodeFun=None, **options):
|
||||||
|
|
||||||
|
if asn1Spec is None:
|
||||||
|
tagSet = value.tagSet
|
||||||
|
else:
|
||||||
|
tagSet = asn1Spec.tagSet
|
||||||
|
|
||||||
|
# untagged item?
|
||||||
|
if not tagSet:
|
||||||
|
substrate, isConstructed, isOctets = self.encodeValue(
|
||||||
|
value, asn1Spec, encodeFun, **options
|
||||||
|
)
|
||||||
|
return substrate
|
||||||
|
|
||||||
|
defMode = options.get('defMode', True)
|
||||||
|
|
||||||
|
substrate = null
|
||||||
|
|
||||||
|
for idx, singleTag in enumerate(tagSet.superTags):
|
||||||
|
|
||||||
|
defModeOverride = defMode
|
||||||
|
|
||||||
|
# base tag?
|
||||||
|
if not idx:
|
||||||
|
try:
|
||||||
|
substrate, isConstructed, isOctets = self.encodeValue(
|
||||||
|
value, asn1Spec, encodeFun, **options
|
||||||
|
)
|
||||||
|
|
||||||
|
except error.PyAsn1Error:
|
||||||
|
exc = sys.exc_info()
|
||||||
|
raise error.PyAsn1Error(
|
||||||
|
'Error encoding %r: %s' % (value, exc[1]))
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('encoded %svalue %s into %s' % (
|
||||||
|
isConstructed and 'constructed ' or '', value, substrate
|
||||||
|
))
|
||||||
|
|
||||||
|
if not substrate and isConstructed and options.get('ifNotEmpty', False):
|
||||||
|
return substrate
|
||||||
|
|
||||||
|
if not isConstructed:
|
||||||
|
defModeOverride = True
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('overridden encoding mode into definitive for primitive type')
|
||||||
|
|
||||||
|
header = self.encodeTag(singleTag, isConstructed)
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('encoded %stag %s into %s' % (
|
||||||
|
isConstructed and 'constructed ' or '',
|
||||||
|
singleTag, debug.hexdump(ints2octs(header))))
|
||||||
|
|
||||||
|
header += self.encodeLength(len(substrate), defModeOverride)
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('encoded %s octets (tag + payload) into %s' % (
|
||||||
|
len(substrate), debug.hexdump(ints2octs(header))))
|
||||||
|
|
||||||
|
if isOctets:
|
||||||
|
substrate = ints2octs(header) + substrate
|
||||||
|
|
||||||
|
if not defModeOverride:
|
||||||
|
substrate += self.eooOctetsSubstrate
|
||||||
|
|
||||||
|
else:
|
||||||
|
substrate = header + substrate
|
||||||
|
|
||||||
|
if not defModeOverride:
|
||||||
|
substrate += self.eooIntegerSubstrate
|
||||||
|
|
||||||
|
if not isOctets:
|
||||||
|
substrate = ints2octs(substrate)
|
||||||
|
|
||||||
|
return substrate
|
||||||
|
|
||||||
|
|
||||||
|
class EndOfOctetsEncoder(AbstractItemEncoder):
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
return null, False, True
|
||||||
|
|
||||||
|
|
||||||
|
class BooleanEncoder(AbstractItemEncoder):
|
||||||
|
supportIndefLenMode = False
|
||||||
|
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
return value and (1,) or (0,), False, False
|
||||||
|
|
||||||
|
|
||||||
|
class IntegerEncoder(AbstractItemEncoder):
|
||||||
|
supportIndefLenMode = False
|
||||||
|
supportCompactZero = False
|
||||||
|
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
if value == 0:
|
||||||
|
if LOG:
|
||||||
|
LOG('encoding %spayload for zero INTEGER' % (
|
||||||
|
self.supportCompactZero and 'no ' or ''
|
||||||
|
))
|
||||||
|
|
||||||
|
# de-facto way to encode zero
|
||||||
|
if self.supportCompactZero:
|
||||||
|
return (), False, False
|
||||||
|
else:
|
||||||
|
return (0,), False, False
|
||||||
|
|
||||||
|
return to_bytes(int(value), signed=True), False, True
|
||||||
|
|
||||||
|
|
||||||
|
class BitStringEncoder(AbstractItemEncoder):
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
if asn1Spec is not None:
|
||||||
|
# TODO: try to avoid ASN.1 schema instantiation
|
||||||
|
value = asn1Spec.clone(value)
|
||||||
|
|
||||||
|
valueLength = len(value)
|
||||||
|
if valueLength % 8:
|
||||||
|
alignedValue = value << (8 - valueLength % 8)
|
||||||
|
else:
|
||||||
|
alignedValue = value
|
||||||
|
|
||||||
|
maxChunkSize = options.get('maxChunkSize', 0)
|
||||||
|
if not maxChunkSize or len(alignedValue) <= maxChunkSize * 8:
|
||||||
|
substrate = alignedValue.asOctets()
|
||||||
|
return int2oct(len(substrate) * 8 - valueLength) + substrate, False, True
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('encoding into up to %s-octet chunks' % maxChunkSize)
|
||||||
|
|
||||||
|
baseTag = value.tagSet.baseTag
|
||||||
|
|
||||||
|
# strip off explicit tags
|
||||||
|
if baseTag:
|
||||||
|
tagSet = tag.TagSet(baseTag, baseTag)
|
||||||
|
|
||||||
|
else:
|
||||||
|
tagSet = tag.TagSet()
|
||||||
|
|
||||||
|
alignedValue = alignedValue.clone(tagSet=tagSet)
|
||||||
|
|
||||||
|
stop = 0
|
||||||
|
substrate = null
|
||||||
|
while stop < valueLength:
|
||||||
|
start = stop
|
||||||
|
stop = min(start + maxChunkSize * 8, valueLength)
|
||||||
|
substrate += encodeFun(alignedValue[start:stop], asn1Spec, **options)
|
||||||
|
|
||||||
|
return substrate, True, True
|
||||||
|
|
||||||
|
|
||||||
|
class OctetStringEncoder(AbstractItemEncoder):
|
||||||
|
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
|
||||||
|
if asn1Spec is None:
|
||||||
|
substrate = value.asOctets()
|
||||||
|
|
||||||
|
elif not isOctetsType(value):
|
||||||
|
substrate = asn1Spec.clone(value).asOctets()
|
||||||
|
|
||||||
|
else:
|
||||||
|
substrate = value
|
||||||
|
|
||||||
|
maxChunkSize = options.get('maxChunkSize', 0)
|
||||||
|
|
||||||
|
if not maxChunkSize or len(substrate) <= maxChunkSize:
|
||||||
|
return substrate, False, True
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('encoding into up to %s-octet chunks' % maxChunkSize)
|
||||||
|
|
||||||
|
# strip off explicit tags for inner chunks
|
||||||
|
|
||||||
|
if asn1Spec is None:
|
||||||
|
baseTag = value.tagSet.baseTag
|
||||||
|
|
||||||
|
# strip off explicit tags
|
||||||
|
if baseTag:
|
||||||
|
tagSet = tag.TagSet(baseTag, baseTag)
|
||||||
|
|
||||||
|
else:
|
||||||
|
tagSet = tag.TagSet()
|
||||||
|
|
||||||
|
asn1Spec = value.clone(tagSet=tagSet)
|
||||||
|
|
||||||
|
elif not isOctetsType(value):
|
||||||
|
baseTag = asn1Spec.tagSet.baseTag
|
||||||
|
|
||||||
|
# strip off explicit tags
|
||||||
|
if baseTag:
|
||||||
|
tagSet = tag.TagSet(baseTag, baseTag)
|
||||||
|
|
||||||
|
else:
|
||||||
|
tagSet = tag.TagSet()
|
||||||
|
|
||||||
|
asn1Spec = asn1Spec.clone(tagSet=tagSet)
|
||||||
|
|
||||||
|
pos = 0
|
||||||
|
substrate = null
|
||||||
|
|
||||||
|
while True:
|
||||||
|
chunk = value[pos:pos + maxChunkSize]
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
|
||||||
|
substrate += encodeFun(chunk, asn1Spec, **options)
|
||||||
|
pos += maxChunkSize
|
||||||
|
|
||||||
|
return substrate, True, True
|
||||||
|
|
||||||
|
|
||||||
|
class NullEncoder(AbstractItemEncoder):
|
||||||
|
supportIndefLenMode = False
|
||||||
|
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
return null, False, True
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectIdentifierEncoder(AbstractItemEncoder):
|
||||||
|
supportIndefLenMode = False
|
||||||
|
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
if asn1Spec is not None:
|
||||||
|
value = asn1Spec.clone(value)
|
||||||
|
|
||||||
|
oid = value.asTuple()
|
||||||
|
|
||||||
|
# Build the first pair
|
||||||
|
try:
|
||||||
|
first = oid[0]
|
||||||
|
second = oid[1]
|
||||||
|
|
||||||
|
except IndexError:
|
||||||
|
raise error.PyAsn1Error('Short OID %s' % (value,))
|
||||||
|
|
||||||
|
if 0 <= second <= 39:
|
||||||
|
if first == 1:
|
||||||
|
oid = (second + 40,) + oid[2:]
|
||||||
|
elif first == 0:
|
||||||
|
oid = (second,) + oid[2:]
|
||||||
|
elif first == 2:
|
||||||
|
oid = (second + 80,) + oid[2:]
|
||||||
|
else:
|
||||||
|
raise error.PyAsn1Error('Impossible first/second arcs at %s' % (value,))
|
||||||
|
|
||||||
|
elif first == 2:
|
||||||
|
oid = (second + 80,) + oid[2:]
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise error.PyAsn1Error('Impossible first/second arcs at %s' % (value,))
|
||||||
|
|
||||||
|
octets = ()
|
||||||
|
|
||||||
|
# Cycle through subIds
|
||||||
|
for subOid in oid:
|
||||||
|
if 0 <= subOid <= 127:
|
||||||
|
# Optimize for the common case
|
||||||
|
octets += (subOid,)
|
||||||
|
|
||||||
|
elif subOid > 127:
|
||||||
|
# Pack large Sub-Object IDs
|
||||||
|
res = (subOid & 0x7f,)
|
||||||
|
subOid >>= 7
|
||||||
|
|
||||||
|
while subOid:
|
||||||
|
res = (0x80 | (subOid & 0x7f),) + res
|
||||||
|
subOid >>= 7
|
||||||
|
|
||||||
|
# Add packed Sub-Object ID to resulted Object ID
|
||||||
|
octets += res
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise error.PyAsn1Error('Negative OID arc %s at %s' % (subOid, value))
|
||||||
|
|
||||||
|
return octets, False, False
|
||||||
|
|
||||||
|
|
||||||
|
class RealEncoder(AbstractItemEncoder):
|
||||||
|
supportIndefLenMode = 0
|
||||||
|
binEncBase = 2 # set to None to choose encoding base automatically
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _dropFloatingPoint(m, encbase, e):
|
||||||
|
ms, es = 1, 1
|
||||||
|
if m < 0:
|
||||||
|
ms = -1 # mantissa sign
|
||||||
|
|
||||||
|
if e < 0:
|
||||||
|
es = -1 # exponent sign
|
||||||
|
|
||||||
|
m *= ms
|
||||||
|
|
||||||
|
if encbase == 8:
|
||||||
|
m *= 2 ** (abs(e) % 3 * es)
|
||||||
|
e = abs(e) // 3 * es
|
||||||
|
|
||||||
|
elif encbase == 16:
|
||||||
|
m *= 2 ** (abs(e) % 4 * es)
|
||||||
|
e = abs(e) // 4 * es
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if int(m) != m:
|
||||||
|
m *= encbase
|
||||||
|
e -= 1
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
|
||||||
|
return ms, int(m), encbase, e
|
||||||
|
|
||||||
|
def _chooseEncBase(self, value):
|
||||||
|
m, b, e = value
|
||||||
|
encBase = [2, 8, 16]
|
||||||
|
if value.binEncBase in encBase:
|
||||||
|
return self._dropFloatingPoint(m, value.binEncBase, e)
|
||||||
|
|
||||||
|
elif self.binEncBase in encBase:
|
||||||
|
return self._dropFloatingPoint(m, self.binEncBase, e)
|
||||||
|
|
||||||
|
# auto choosing base 2/8/16
|
||||||
|
mantissa = [m, m, m]
|
||||||
|
exponent = [e, e, e]
|
||||||
|
sign = 1
|
||||||
|
encbase = 2
|
||||||
|
e = float('inf')
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
(sign,
|
||||||
|
mantissa[i],
|
||||||
|
encBase[i],
|
||||||
|
exponent[i]) = self._dropFloatingPoint(mantissa[i], encBase[i], exponent[i])
|
||||||
|
|
||||||
|
if abs(exponent[i]) < abs(e) or (abs(exponent[i]) == abs(e) and mantissa[i] < m):
|
||||||
|
e = exponent[i]
|
||||||
|
m = int(mantissa[i])
|
||||||
|
encbase = encBase[i]
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('automatically chosen REAL encoding base %s, sign %s, mantissa %s, '
|
||||||
|
'exponent %s' % (encbase, sign, m, e))
|
||||||
|
|
||||||
|
return sign, m, encbase, e
|
||||||
|
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
if asn1Spec is not None:
|
||||||
|
value = asn1Spec.clone(value)
|
||||||
|
|
||||||
|
if value.isPlusInf:
|
||||||
|
return (0x40,), False, False
|
||||||
|
|
||||||
|
if value.isMinusInf:
|
||||||
|
return (0x41,), False, False
|
||||||
|
|
||||||
|
m, b, e = value
|
||||||
|
|
||||||
|
if not m:
|
||||||
|
return null, False, True
|
||||||
|
|
||||||
|
if b == 10:
|
||||||
|
if LOG:
|
||||||
|
LOG('encoding REAL into character form')
|
||||||
|
|
||||||
|
return str2octs('\x03%dE%s%d' % (m, e == 0 and '+' or '', e)), False, True
|
||||||
|
|
||||||
|
elif b == 2:
|
||||||
|
fo = 0x80 # binary encoding
|
||||||
|
ms, m, encbase, e = self._chooseEncBase(value)
|
||||||
|
|
||||||
|
if ms < 0: # mantissa sign
|
||||||
|
fo |= 0x40 # sign bit
|
||||||
|
|
||||||
|
# exponent & mantissa normalization
|
||||||
|
if encbase == 2:
|
||||||
|
while m & 0x1 == 0:
|
||||||
|
m >>= 1
|
||||||
|
e += 1
|
||||||
|
|
||||||
|
elif encbase == 8:
|
||||||
|
while m & 0x7 == 0:
|
||||||
|
m >>= 3
|
||||||
|
e += 1
|
||||||
|
fo |= 0x10
|
||||||
|
|
||||||
|
else: # encbase = 16
|
||||||
|
while m & 0xf == 0:
|
||||||
|
m >>= 4
|
||||||
|
e += 1
|
||||||
|
fo |= 0x20
|
||||||
|
|
||||||
|
sf = 0 # scale factor
|
||||||
|
|
||||||
|
while m & 0x1 == 0:
|
||||||
|
m >>= 1
|
||||||
|
sf += 1
|
||||||
|
|
||||||
|
if sf > 3:
|
||||||
|
raise error.PyAsn1Error('Scale factor overflow') # bug if raised
|
||||||
|
|
||||||
|
fo |= sf << 2
|
||||||
|
eo = null
|
||||||
|
if e == 0 or e == -1:
|
||||||
|
eo = int2oct(e & 0xff)
|
||||||
|
|
||||||
|
else:
|
||||||
|
while e not in (0, -1):
|
||||||
|
eo = int2oct(e & 0xff) + eo
|
||||||
|
e >>= 8
|
||||||
|
|
||||||
|
if e == 0 and eo and oct2int(eo[0]) & 0x80:
|
||||||
|
eo = int2oct(0) + eo
|
||||||
|
|
||||||
|
if e == -1 and eo and not (oct2int(eo[0]) & 0x80):
|
||||||
|
eo = int2oct(0xff) + eo
|
||||||
|
|
||||||
|
n = len(eo)
|
||||||
|
if n > 0xff:
|
||||||
|
raise error.PyAsn1Error('Real exponent overflow')
|
||||||
|
|
||||||
|
if n == 1:
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif n == 2:
|
||||||
|
fo |= 1
|
||||||
|
|
||||||
|
elif n == 3:
|
||||||
|
fo |= 2
|
||||||
|
|
||||||
|
else:
|
||||||
|
fo |= 3
|
||||||
|
eo = int2oct(n & 0xff) + eo
|
||||||
|
|
||||||
|
po = null
|
||||||
|
|
||||||
|
while m:
|
||||||
|
po = int2oct(m & 0xff) + po
|
||||||
|
m >>= 8
|
||||||
|
|
||||||
|
substrate = int2oct(fo) + eo + po
|
||||||
|
|
||||||
|
return substrate, False, True
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise error.PyAsn1Error('Prohibited Real base %s' % b)
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceEncoder(AbstractItemEncoder):
|
||||||
|
omitEmptyOptionals = False
|
||||||
|
|
||||||
|
# TODO: handling three flavors of input is too much -- split over codecs
|
||||||
|
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
|
||||||
|
substrate = null
|
||||||
|
|
||||||
|
omitEmptyOptionals = options.get(
|
||||||
|
'omitEmptyOptionals', self.omitEmptyOptionals)
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('%sencoding empty OPTIONAL components' % (
|
||||||
|
omitEmptyOptionals and 'not ' or ''))
|
||||||
|
|
||||||
|
if asn1Spec is None:
|
||||||
|
# instance of ASN.1 schema
|
||||||
|
inconsistency = value.isInconsistent
|
||||||
|
if inconsistency:
|
||||||
|
raise inconsistency
|
||||||
|
|
||||||
|
namedTypes = value.componentType
|
||||||
|
|
||||||
|
for idx, component in enumerate(value.values()):
|
||||||
|
if namedTypes:
|
||||||
|
namedType = namedTypes[idx]
|
||||||
|
|
||||||
|
if namedType.isOptional and not component.isValue:
|
||||||
|
if LOG:
|
||||||
|
LOG('not encoding OPTIONAL component %r' % (namedType,))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if namedType.isDefaulted and component == namedType.asn1Object:
|
||||||
|
if LOG:
|
||||||
|
LOG('not encoding DEFAULT component %r' % (namedType,))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if omitEmptyOptionals:
|
||||||
|
options.update(ifNotEmpty=namedType.isOptional)
|
||||||
|
|
||||||
|
# wrap open type blob if needed
|
||||||
|
if namedTypes and namedType.openType:
|
||||||
|
|
||||||
|
wrapType = namedType.asn1Object
|
||||||
|
|
||||||
|
if wrapType.typeId in (
|
||||||
|
univ.SetOf.typeId, univ.SequenceOf.typeId):
|
||||||
|
|
||||||
|
substrate += encodeFun(
|
||||||
|
component, asn1Spec,
|
||||||
|
**dict(options, wrapType=wrapType.componentType))
|
||||||
|
|
||||||
|
else:
|
||||||
|
chunk = encodeFun(component, asn1Spec, **options)
|
||||||
|
|
||||||
|
if wrapType.isSameTypeWith(component):
|
||||||
|
substrate += chunk
|
||||||
|
|
||||||
|
else:
|
||||||
|
substrate += encodeFun(chunk, wrapType, **options)
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('wrapped with wrap type %r' % (wrapType,))
|
||||||
|
|
||||||
|
else:
|
||||||
|
substrate += encodeFun(component, asn1Spec, **options)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# bare Python value + ASN.1 schema
|
||||||
|
for idx, namedType in enumerate(asn1Spec.componentType.namedTypes):
|
||||||
|
|
||||||
|
try:
|
||||||
|
component = value[namedType.name]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
raise error.PyAsn1Error('Component name "%s" not found in %r' % (
|
||||||
|
namedType.name, value))
|
||||||
|
|
||||||
|
if namedType.isOptional and namedType.name not in value:
|
||||||
|
if LOG:
|
||||||
|
LOG('not encoding OPTIONAL component %r' % (namedType,))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if namedType.isDefaulted and component == namedType.asn1Object:
|
||||||
|
if LOG:
|
||||||
|
LOG('not encoding DEFAULT component %r' % (namedType,))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if omitEmptyOptionals:
|
||||||
|
options.update(ifNotEmpty=namedType.isOptional)
|
||||||
|
|
||||||
|
componentSpec = namedType.asn1Object
|
||||||
|
|
||||||
|
# wrap open type blob if needed
|
||||||
|
if namedType.openType:
|
||||||
|
|
||||||
|
if componentSpec.typeId in (
|
||||||
|
univ.SetOf.typeId, univ.SequenceOf.typeId):
|
||||||
|
|
||||||
|
substrate += encodeFun(
|
||||||
|
component, componentSpec,
|
||||||
|
**dict(options, wrapType=componentSpec.componentType))
|
||||||
|
|
||||||
|
else:
|
||||||
|
chunk = encodeFun(component, componentSpec, **options)
|
||||||
|
|
||||||
|
if componentSpec.isSameTypeWith(component):
|
||||||
|
substrate += chunk
|
||||||
|
|
||||||
|
else:
|
||||||
|
substrate += encodeFun(chunk, componentSpec, **options)
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('wrapped with wrap type %r' % (componentSpec,))
|
||||||
|
|
||||||
|
else:
|
||||||
|
substrate += encodeFun(component, componentSpec, **options)
|
||||||
|
|
||||||
|
return substrate, True, True
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceOfEncoder(AbstractItemEncoder):
|
||||||
|
def _encodeComponents(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
|
||||||
|
if asn1Spec is None:
|
||||||
|
inconsistency = value.isInconsistent
|
||||||
|
if inconsistency:
|
||||||
|
raise inconsistency
|
||||||
|
|
||||||
|
else:
|
||||||
|
asn1Spec = asn1Spec.componentType
|
||||||
|
|
||||||
|
chunks = []
|
||||||
|
|
||||||
|
wrapType = options.pop('wrapType', None)
|
||||||
|
|
||||||
|
for idx, component in enumerate(value):
|
||||||
|
chunk = encodeFun(component, asn1Spec, **options)
|
||||||
|
|
||||||
|
if (wrapType is not None and
|
||||||
|
not wrapType.isSameTypeWith(component)):
|
||||||
|
# wrap encoded value with wrapper container (e.g. ANY)
|
||||||
|
chunk = encodeFun(chunk, wrapType, **options)
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('wrapped with wrap type %r' % (wrapType,))
|
||||||
|
|
||||||
|
chunks.append(chunk)
|
||||||
|
|
||||||
|
return chunks
|
||||||
|
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
chunks = self._encodeComponents(
|
||||||
|
value, asn1Spec, encodeFun, **options)
|
||||||
|
|
||||||
|
return null.join(chunks), True, True
|
||||||
|
|
||||||
|
|
||||||
|
class ChoiceEncoder(AbstractItemEncoder):
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
if asn1Spec is None:
|
||||||
|
component = value.getComponent()
|
||||||
|
else:
|
||||||
|
names = [namedType.name for namedType in asn1Spec.componentType.namedTypes
|
||||||
|
if namedType.name in value]
|
||||||
|
if len(names) != 1:
|
||||||
|
raise error.PyAsn1Error('%s components for Choice at %r' % (len(names) and 'Multiple ' or 'None ', value))
|
||||||
|
|
||||||
|
name = names[0]
|
||||||
|
|
||||||
|
component = value[name]
|
||||||
|
asn1Spec = asn1Spec[name]
|
||||||
|
|
||||||
|
return encodeFun(component, asn1Spec, **options), True, True
|
||||||
|
|
||||||
|
|
||||||
|
class AnyEncoder(OctetStringEncoder):
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
if asn1Spec is None:
|
||||||
|
value = value.asOctets()
|
||||||
|
elif not isOctetsType(value):
|
||||||
|
value = asn1Spec.clone(value).asOctets()
|
||||||
|
|
||||||
|
return value, not options.get('defMode', True), True
|
||||||
|
|
||||||
|
|
||||||
|
tagMap = {
|
||||||
|
eoo.endOfOctets.tagSet: EndOfOctetsEncoder(),
|
||||||
|
univ.Boolean.tagSet: BooleanEncoder(),
|
||||||
|
univ.Integer.tagSet: IntegerEncoder(),
|
||||||
|
univ.BitString.tagSet: BitStringEncoder(),
|
||||||
|
univ.OctetString.tagSet: OctetStringEncoder(),
|
||||||
|
univ.Null.tagSet: NullEncoder(),
|
||||||
|
univ.ObjectIdentifier.tagSet: ObjectIdentifierEncoder(),
|
||||||
|
univ.Enumerated.tagSet: IntegerEncoder(),
|
||||||
|
univ.Real.tagSet: RealEncoder(),
|
||||||
|
# Sequence & Set have same tags as SequenceOf & SetOf
|
||||||
|
univ.SequenceOf.tagSet: SequenceOfEncoder(),
|
||||||
|
univ.SetOf.tagSet: SequenceOfEncoder(),
|
||||||
|
univ.Choice.tagSet: ChoiceEncoder(),
|
||||||
|
# character string types
|
||||||
|
char.UTF8String.tagSet: OctetStringEncoder(),
|
||||||
|
char.NumericString.tagSet: OctetStringEncoder(),
|
||||||
|
char.PrintableString.tagSet: OctetStringEncoder(),
|
||||||
|
char.TeletexString.tagSet: OctetStringEncoder(),
|
||||||
|
char.VideotexString.tagSet: OctetStringEncoder(),
|
||||||
|
char.IA5String.tagSet: OctetStringEncoder(),
|
||||||
|
char.GraphicString.tagSet: OctetStringEncoder(),
|
||||||
|
char.VisibleString.tagSet: OctetStringEncoder(),
|
||||||
|
char.GeneralString.tagSet: OctetStringEncoder(),
|
||||||
|
char.UniversalString.tagSet: OctetStringEncoder(),
|
||||||
|
char.BMPString.tagSet: OctetStringEncoder(),
|
||||||
|
# useful types
|
||||||
|
useful.ObjectDescriptor.tagSet: OctetStringEncoder(),
|
||||||
|
useful.GeneralizedTime.tagSet: OctetStringEncoder(),
|
||||||
|
useful.UTCTime.tagSet: OctetStringEncoder()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Put in ambiguous & non-ambiguous types for faster codec lookup
|
||||||
|
typeMap = {
|
||||||
|
univ.Boolean.typeId: BooleanEncoder(),
|
||||||
|
univ.Integer.typeId: IntegerEncoder(),
|
||||||
|
univ.BitString.typeId: BitStringEncoder(),
|
||||||
|
univ.OctetString.typeId: OctetStringEncoder(),
|
||||||
|
univ.Null.typeId: NullEncoder(),
|
||||||
|
univ.ObjectIdentifier.typeId: ObjectIdentifierEncoder(),
|
||||||
|
univ.Enumerated.typeId: IntegerEncoder(),
|
||||||
|
univ.Real.typeId: RealEncoder(),
|
||||||
|
# Sequence & Set have same tags as SequenceOf & SetOf
|
||||||
|
univ.Set.typeId: SequenceEncoder(),
|
||||||
|
univ.SetOf.typeId: SequenceOfEncoder(),
|
||||||
|
univ.Sequence.typeId: SequenceEncoder(),
|
||||||
|
univ.SequenceOf.typeId: SequenceOfEncoder(),
|
||||||
|
univ.Choice.typeId: ChoiceEncoder(),
|
||||||
|
univ.Any.typeId: AnyEncoder(),
|
||||||
|
# character string types
|
||||||
|
char.UTF8String.typeId: OctetStringEncoder(),
|
||||||
|
char.NumericString.typeId: OctetStringEncoder(),
|
||||||
|
char.PrintableString.typeId: OctetStringEncoder(),
|
||||||
|
char.TeletexString.typeId: OctetStringEncoder(),
|
||||||
|
char.VideotexString.typeId: OctetStringEncoder(),
|
||||||
|
char.IA5String.typeId: OctetStringEncoder(),
|
||||||
|
char.GraphicString.typeId: OctetStringEncoder(),
|
||||||
|
char.VisibleString.typeId: OctetStringEncoder(),
|
||||||
|
char.GeneralString.typeId: OctetStringEncoder(),
|
||||||
|
char.UniversalString.typeId: OctetStringEncoder(),
|
||||||
|
char.BMPString.typeId: OctetStringEncoder(),
|
||||||
|
# useful types
|
||||||
|
useful.ObjectDescriptor.typeId: OctetStringEncoder(),
|
||||||
|
useful.GeneralizedTime.typeId: OctetStringEncoder(),
|
||||||
|
useful.UTCTime.typeId: OctetStringEncoder()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Encoder(object):
|
||||||
|
fixedDefLengthMode = None
|
||||||
|
fixedChunkSize = None
|
||||||
|
|
||||||
|
# noinspection PyDefaultArgument
|
||||||
|
def __init__(self, tagMap, typeMap={}):
|
||||||
|
self.__tagMap = tagMap
|
||||||
|
self.__typeMap = typeMap
|
||||||
|
|
||||||
|
def __call__(self, value, asn1Spec=None, **options):
|
||||||
|
try:
|
||||||
|
if asn1Spec is None:
|
||||||
|
typeId = value.typeId
|
||||||
|
else:
|
||||||
|
typeId = asn1Spec.typeId
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
raise error.PyAsn1Error('Value %r is not ASN.1 type instance '
|
||||||
|
'and "asn1Spec" not given' % (value,))
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('encoder called in %sdef mode, chunk size %s for '
|
||||||
|
'type %s, value:\n%s' % (not options.get('defMode', True) and 'in' or '', options.get('maxChunkSize', 0), asn1Spec is None and value.prettyPrintType() or asn1Spec.prettyPrintType(), value))
|
||||||
|
|
||||||
|
if self.fixedDefLengthMode is not None:
|
||||||
|
options.update(defMode=self.fixedDefLengthMode)
|
||||||
|
|
||||||
|
if self.fixedChunkSize is not None:
|
||||||
|
options.update(maxChunkSize=self.fixedChunkSize)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
concreteEncoder = self.__typeMap[typeId]
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('using value codec %s chosen by type ID %s' % (concreteEncoder.__class__.__name__, typeId))
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
if asn1Spec is None:
|
||||||
|
tagSet = value.tagSet
|
||||||
|
else:
|
||||||
|
tagSet = asn1Spec.tagSet
|
||||||
|
|
||||||
|
# use base type for codec lookup to recover untagged types
|
||||||
|
baseTagSet = tag.TagSet(tagSet.baseTag, tagSet.baseTag)
|
||||||
|
|
||||||
|
try:
|
||||||
|
concreteEncoder = self.__tagMap[baseTagSet]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
raise error.PyAsn1Error('No encoder for %r (%s)' % (value, tagSet))
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('using value codec %s chosen by tagSet %s' % (concreteEncoder.__class__.__name__, tagSet))
|
||||||
|
|
||||||
|
substrate = concreteEncoder.encode(value, asn1Spec, self, **options)
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('codec %s built %s octets of substrate: %s\nencoder completed' % (concreteEncoder, len(substrate), debug.hexdump(substrate)))
|
||||||
|
|
||||||
|
return substrate
|
||||||
|
|
||||||
|
#: Turns ASN.1 object into BER octet stream.
|
||||||
|
#:
|
||||||
|
#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
|
||||||
|
#: walks all its components recursively and produces a BER octet stream.
|
||||||
|
#:
|
||||||
|
#: Parameters
|
||||||
|
#: ----------
|
||||||
|
#: value: either a Python or pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
|
||||||
|
#: A Python or pyasn1 object to encode. If Python object is given, `asnSpec`
|
||||||
|
#: parameter is required to guide the encoding process.
|
||||||
|
#:
|
||||||
|
#: Keyword Args
|
||||||
|
#: ------------
|
||||||
|
#: asn1Spec:
|
||||||
|
#: Optional ASN.1 schema or value object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative
|
||||||
|
#:
|
||||||
|
#: defMode: :py:class:`bool`
|
||||||
|
#: If :obj:`False`, produces indefinite length encoding
|
||||||
|
#:
|
||||||
|
#: maxChunkSize: :py:class:`int`
|
||||||
|
#: Maximum chunk size in chunked encoding mode (0 denotes unlimited chunk size)
|
||||||
|
#:
|
||||||
|
#: Returns
|
||||||
|
#: -------
|
||||||
|
#: : :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2)
|
||||||
|
#: Given ASN.1 object encoded into BER octetstream
|
||||||
|
#:
|
||||||
|
#: Raises
|
||||||
|
#: ------
|
||||||
|
#: ~pyasn1.error.PyAsn1Error
|
||||||
|
#: On encoding errors
|
||||||
|
#:
|
||||||
|
#: Examples
|
||||||
|
#: --------
|
||||||
|
#: Encode Python value into BER with ASN.1 schema
|
||||||
|
#:
|
||||||
|
#: .. code-block:: pycon
|
||||||
|
#:
|
||||||
|
#: >>> seq = SequenceOf(componentType=Integer())
|
||||||
|
#: >>> encode([1, 2, 3], asn1Spec=seq)
|
||||||
|
#: b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03'
|
||||||
|
#:
|
||||||
|
#: Encode ASN.1 value object into BER
|
||||||
|
#:
|
||||||
|
#: .. code-block:: pycon
|
||||||
|
#:
|
||||||
|
#: >>> seq = SequenceOf(componentType=Integer())
|
||||||
|
#: >>> seq.extend([1, 2, 3])
|
||||||
|
#: >>> encode(seq)
|
||||||
|
#: b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03'
|
||||||
|
#:
|
||||||
|
encode = Encoder(tagMap, typeMap)
|
||||||
28
pyasn1/codec/ber/eoo.py
Normal file
28
pyasn1/codec/ber/eoo.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
from pyasn1.type import base
|
||||||
|
from pyasn1.type import tag
|
||||||
|
|
||||||
|
__all__ = ['endOfOctets']
|
||||||
|
|
||||||
|
|
||||||
|
class EndOfOctets(base.SimpleAsn1Type):
|
||||||
|
defaultValue = 0
|
||||||
|
tagSet = tag.initTagSet(
|
||||||
|
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x00)
|
||||||
|
)
|
||||||
|
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = object.__new__(cls, *args, **kwargs)
|
||||||
|
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
|
||||||
|
endOfOctets = EndOfOctets()
|
||||||
1
pyasn1/codec/cer/__init__.py
Normal file
1
pyasn1/codec/cer/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# This file is necessary to make this directory a package.
|
||||||
114
pyasn1/codec/cer/decoder.py
Normal file
114
pyasn1/codec/cer/decoder.py
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
from pyasn1 import error
|
||||||
|
from pyasn1.codec.ber import decoder
|
||||||
|
from pyasn1.compat.octets import oct2int
|
||||||
|
from pyasn1.type import univ
|
||||||
|
|
||||||
|
__all__ = ['decode']
|
||||||
|
|
||||||
|
|
||||||
|
class BooleanDecoder(decoder.AbstractSimpleDecoder):
|
||||||
|
protoComponent = univ.Boolean(0)
|
||||||
|
|
||||||
|
def valueDecoder(self, substrate, asn1Spec,
|
||||||
|
tagSet=None, length=None, state=None,
|
||||||
|
decodeFun=None, substrateFun=None,
|
||||||
|
**options):
|
||||||
|
head, tail = substrate[:length], substrate[length:]
|
||||||
|
if not head or length != 1:
|
||||||
|
raise error.PyAsn1Error('Not single-octet Boolean payload')
|
||||||
|
byte = oct2int(head[0])
|
||||||
|
# CER/DER specifies encoding of TRUE as 0xFF and FALSE as 0x0, while
|
||||||
|
# BER allows any non-zero value as TRUE; cf. sections 8.2.2. and 11.1
|
||||||
|
# in https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf
|
||||||
|
if byte == 0xff:
|
||||||
|
value = 1
|
||||||
|
elif byte == 0x00:
|
||||||
|
value = 0
|
||||||
|
else:
|
||||||
|
raise error.PyAsn1Error('Unexpected Boolean payload: %s' % byte)
|
||||||
|
return self._createComponent(asn1Spec, tagSet, value, **options), tail
|
||||||
|
|
||||||
|
# TODO: prohibit non-canonical encoding
|
||||||
|
BitStringDecoder = decoder.BitStringDecoder
|
||||||
|
OctetStringDecoder = decoder.OctetStringDecoder
|
||||||
|
RealDecoder = decoder.RealDecoder
|
||||||
|
|
||||||
|
tagMap = decoder.tagMap.copy()
|
||||||
|
tagMap.update(
|
||||||
|
{univ.Boolean.tagSet: BooleanDecoder(),
|
||||||
|
univ.BitString.tagSet: BitStringDecoder(),
|
||||||
|
univ.OctetString.tagSet: OctetStringDecoder(),
|
||||||
|
univ.Real.tagSet: RealDecoder()}
|
||||||
|
)
|
||||||
|
|
||||||
|
typeMap = decoder.typeMap.copy()
|
||||||
|
|
||||||
|
# Put in non-ambiguous types for faster codec lookup
|
||||||
|
for typeDecoder in tagMap.values():
|
||||||
|
if typeDecoder.protoComponent is not None:
|
||||||
|
typeId = typeDecoder.protoComponent.__class__.typeId
|
||||||
|
if typeId is not None and typeId not in typeMap:
|
||||||
|
typeMap[typeId] = typeDecoder
|
||||||
|
|
||||||
|
|
||||||
|
class Decoder(decoder.Decoder):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
#: Turns CER octet stream into an ASN.1 object.
|
||||||
|
#:
|
||||||
|
#: Takes CER octet-stream and decode it into an ASN.1 object
|
||||||
|
#: (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which
|
||||||
|
#: may be a scalar or an arbitrary nested structure.
|
||||||
|
#:
|
||||||
|
#: Parameters
|
||||||
|
#: ----------
|
||||||
|
#: substrate: :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2)
|
||||||
|
#: CER octet-stream
|
||||||
|
#:
|
||||||
|
#: Keyword Args
|
||||||
|
#: ------------
|
||||||
|
#: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative
|
||||||
|
#: A pyasn1 type object to act as a template guiding the decoder. Depending on the ASN.1 structure
|
||||||
|
#: being decoded, *asn1Spec* may or may not be required. Most common reason for
|
||||||
|
#: it to require is that ASN.1 structure is encoded in *IMPLICIT* tagging mode.
|
||||||
|
#:
|
||||||
|
#: Returns
|
||||||
|
#: -------
|
||||||
|
#: : :py:class:`tuple`
|
||||||
|
#: A tuple of pyasn1 object recovered from CER substrate (:py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
|
||||||
|
#: and the unprocessed trailing portion of the *substrate* (may be empty)
|
||||||
|
#:
|
||||||
|
#: Raises
|
||||||
|
#: ------
|
||||||
|
#: ~pyasn1.error.PyAsn1Error, ~pyasn1.error.SubstrateUnderrunError
|
||||||
|
#: On decoding errors
|
||||||
|
#:
|
||||||
|
#: Examples
|
||||||
|
#: --------
|
||||||
|
#: Decode CER serialisation without ASN.1 schema
|
||||||
|
#:
|
||||||
|
#: .. code-block:: pycon
|
||||||
|
#:
|
||||||
|
#: >>> s, _ = decode(b'0\x80\x02\x01\x01\x02\x01\x02\x02\x01\x03\x00\x00')
|
||||||
|
#: >>> str(s)
|
||||||
|
#: SequenceOf:
|
||||||
|
#: 1 2 3
|
||||||
|
#:
|
||||||
|
#: Decode CER serialisation with ASN.1 schema
|
||||||
|
#:
|
||||||
|
#: .. code-block:: pycon
|
||||||
|
#:
|
||||||
|
#: >>> seq = SequenceOf(componentType=Integer())
|
||||||
|
#: >>> s, _ = decode(b'0\x80\x02\x01\x01\x02\x01\x02\x02\x01\x03\x00\x00', asn1Spec=seq)
|
||||||
|
#: >>> str(s)
|
||||||
|
#: SequenceOf:
|
||||||
|
#: 1 2 3
|
||||||
|
#:
|
||||||
|
decode = Decoder(tagMap, decoder.typeMap)
|
||||||
313
pyasn1/codec/cer/encoder.py
Normal file
313
pyasn1/codec/cer/encoder.py
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
from pyasn1 import error
|
||||||
|
from pyasn1.codec.ber import encoder
|
||||||
|
from pyasn1.compat.octets import str2octs, null
|
||||||
|
from pyasn1.type import univ
|
||||||
|
from pyasn1.type import useful
|
||||||
|
|
||||||
|
__all__ = ['encode']
|
||||||
|
|
||||||
|
|
||||||
|
class BooleanEncoder(encoder.IntegerEncoder):
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
if value == 0:
|
||||||
|
substrate = (0,)
|
||||||
|
else:
|
||||||
|
substrate = (255,)
|
||||||
|
return substrate, False, False
|
||||||
|
|
||||||
|
|
||||||
|
class RealEncoder(encoder.RealEncoder):
|
||||||
|
def _chooseEncBase(self, value):
|
||||||
|
m, b, e = value
|
||||||
|
return self._dropFloatingPoint(m, b, e)
|
||||||
|
|
||||||
|
|
||||||
|
# specialized GeneralStringEncoder here
|
||||||
|
|
||||||
|
class TimeEncoderMixIn(object):
|
||||||
|
Z_CHAR = ord('Z')
|
||||||
|
PLUS_CHAR = ord('+')
|
||||||
|
MINUS_CHAR = ord('-')
|
||||||
|
COMMA_CHAR = ord(',')
|
||||||
|
DOT_CHAR = ord('.')
|
||||||
|
ZERO_CHAR = ord('0')
|
||||||
|
|
||||||
|
MIN_LENGTH = 12
|
||||||
|
MAX_LENGTH = 19
|
||||||
|
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
# CER encoding constraints:
|
||||||
|
# - minutes are mandatory, seconds are optional
|
||||||
|
# - sub-seconds must NOT be zero / no meaningless zeros
|
||||||
|
# - no hanging fraction dot
|
||||||
|
# - time in UTC (Z)
|
||||||
|
# - only dot is allowed for fractions
|
||||||
|
|
||||||
|
if asn1Spec is not None:
|
||||||
|
value = asn1Spec.clone(value)
|
||||||
|
|
||||||
|
numbers = value.asNumbers()
|
||||||
|
|
||||||
|
if self.PLUS_CHAR in numbers or self.MINUS_CHAR in numbers:
|
||||||
|
raise error.PyAsn1Error('Must be UTC time: %r' % value)
|
||||||
|
|
||||||
|
if numbers[-1] != self.Z_CHAR:
|
||||||
|
raise error.PyAsn1Error('Missing "Z" time zone specifier: %r' % value)
|
||||||
|
|
||||||
|
if self.COMMA_CHAR in numbers:
|
||||||
|
raise error.PyAsn1Error('Comma in fractions disallowed: %r' % value)
|
||||||
|
|
||||||
|
if self.DOT_CHAR in numbers:
|
||||||
|
|
||||||
|
isModified = False
|
||||||
|
|
||||||
|
numbers = list(numbers)
|
||||||
|
|
||||||
|
searchIndex = min(numbers.index(self.DOT_CHAR) + 4, len(numbers) - 1)
|
||||||
|
|
||||||
|
while numbers[searchIndex] != self.DOT_CHAR:
|
||||||
|
if numbers[searchIndex] == self.ZERO_CHAR:
|
||||||
|
del numbers[searchIndex]
|
||||||
|
isModified = True
|
||||||
|
|
||||||
|
searchIndex -= 1
|
||||||
|
|
||||||
|
searchIndex += 1
|
||||||
|
|
||||||
|
if searchIndex < len(numbers):
|
||||||
|
if numbers[searchIndex] == self.Z_CHAR:
|
||||||
|
# drop hanging comma
|
||||||
|
del numbers[searchIndex - 1]
|
||||||
|
isModified = True
|
||||||
|
|
||||||
|
if isModified:
|
||||||
|
value = value.clone(numbers)
|
||||||
|
|
||||||
|
if not self.MIN_LENGTH < len(numbers) < self.MAX_LENGTH:
|
||||||
|
raise error.PyAsn1Error('Length constraint violated: %r' % value)
|
||||||
|
|
||||||
|
options.update(maxChunkSize=1000)
|
||||||
|
|
||||||
|
return encoder.OctetStringEncoder.encodeValue(
|
||||||
|
self, value, asn1Spec, encodeFun, **options
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GeneralizedTimeEncoder(TimeEncoderMixIn, encoder.OctetStringEncoder):
|
||||||
|
MIN_LENGTH = 12
|
||||||
|
MAX_LENGTH = 20
|
||||||
|
|
||||||
|
|
||||||
|
class UTCTimeEncoder(TimeEncoderMixIn, encoder.OctetStringEncoder):
|
||||||
|
MIN_LENGTH = 10
|
||||||
|
MAX_LENGTH = 14
|
||||||
|
|
||||||
|
|
||||||
|
class SetOfEncoder(encoder.SequenceOfEncoder):
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
chunks = self._encodeComponents(
|
||||||
|
value, asn1Spec, encodeFun, **options)
|
||||||
|
|
||||||
|
# sort by serialised and padded components
|
||||||
|
if len(chunks) > 1:
|
||||||
|
zero = str2octs('\x00')
|
||||||
|
maxLen = max(map(len, chunks))
|
||||||
|
paddedChunks = [
|
||||||
|
(x.ljust(maxLen, zero), x) for x in chunks
|
||||||
|
]
|
||||||
|
paddedChunks.sort(key=lambda x: x[0])
|
||||||
|
|
||||||
|
chunks = [x[1] for x in paddedChunks]
|
||||||
|
|
||||||
|
return null.join(chunks), True, True
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceOfEncoder(encoder.SequenceOfEncoder):
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
|
||||||
|
if options.get('ifNotEmpty', False) and not len(value):
|
||||||
|
return null, True, True
|
||||||
|
|
||||||
|
chunks = self._encodeComponents(
|
||||||
|
value, asn1Spec, encodeFun, **options)
|
||||||
|
|
||||||
|
return null.join(chunks), True, True
|
||||||
|
|
||||||
|
|
||||||
|
class SetEncoder(encoder.SequenceEncoder):
|
||||||
|
@staticmethod
|
||||||
|
def _componentSortKey(componentAndType):
|
||||||
|
"""Sort SET components by tag
|
||||||
|
|
||||||
|
Sort regardless of the Choice value (static sort)
|
||||||
|
"""
|
||||||
|
component, asn1Spec = componentAndType
|
||||||
|
|
||||||
|
if asn1Spec is None:
|
||||||
|
asn1Spec = component
|
||||||
|
|
||||||
|
if asn1Spec.typeId == univ.Choice.typeId and not asn1Spec.tagSet:
|
||||||
|
if asn1Spec.tagSet:
|
||||||
|
return asn1Spec.tagSet
|
||||||
|
else:
|
||||||
|
return asn1Spec.componentType.minTagSet
|
||||||
|
else:
|
||||||
|
return asn1Spec.tagSet
|
||||||
|
|
||||||
|
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||||
|
|
||||||
|
substrate = null
|
||||||
|
|
||||||
|
comps = []
|
||||||
|
compsMap = {}
|
||||||
|
|
||||||
|
if asn1Spec is None:
|
||||||
|
# instance of ASN.1 schema
|
||||||
|
inconsistency = value.isInconsistent
|
||||||
|
if inconsistency:
|
||||||
|
raise inconsistency
|
||||||
|
|
||||||
|
namedTypes = value.componentType
|
||||||
|
|
||||||
|
for idx, component in enumerate(value.values()):
|
||||||
|
if namedTypes:
|
||||||
|
namedType = namedTypes[idx]
|
||||||
|
|
||||||
|
if namedType.isOptional and not component.isValue:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if namedType.isDefaulted and component == namedType.asn1Object:
|
||||||
|
continue
|
||||||
|
|
||||||
|
compsMap[id(component)] = namedType
|
||||||
|
|
||||||
|
else:
|
||||||
|
compsMap[id(component)] = None
|
||||||
|
|
||||||
|
comps.append((component, asn1Spec))
|
||||||
|
|
||||||
|
else:
|
||||||
|
# bare Python value + ASN.1 schema
|
||||||
|
for idx, namedType in enumerate(asn1Spec.componentType.namedTypes):
|
||||||
|
|
||||||
|
try:
|
||||||
|
component = value[namedType.name]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
raise error.PyAsn1Error('Component name "%s" not found in %r' % (namedType.name, value))
|
||||||
|
|
||||||
|
if namedType.isOptional and namedType.name not in value:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if namedType.isDefaulted and component == namedType.asn1Object:
|
||||||
|
continue
|
||||||
|
|
||||||
|
compsMap[id(component)] = namedType
|
||||||
|
comps.append((component, asn1Spec[idx]))
|
||||||
|
|
||||||
|
for comp, compType in sorted(comps, key=self._componentSortKey):
|
||||||
|
namedType = compsMap[id(comp)]
|
||||||
|
|
||||||
|
if namedType:
|
||||||
|
options.update(ifNotEmpty=namedType.isOptional)
|
||||||
|
|
||||||
|
chunk = encodeFun(comp, compType, **options)
|
||||||
|
|
||||||
|
# wrap open type blob if needed
|
||||||
|
if namedType and namedType.openType:
|
||||||
|
wrapType = namedType.asn1Object
|
||||||
|
if wrapType.tagSet and not wrapType.isSameTypeWith(comp):
|
||||||
|
chunk = encodeFun(chunk, wrapType, **options)
|
||||||
|
|
||||||
|
substrate += chunk
|
||||||
|
|
||||||
|
return substrate, True, True
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceEncoder(encoder.SequenceEncoder):
|
||||||
|
omitEmptyOptionals = True
|
||||||
|
|
||||||
|
|
||||||
|
tagMap = encoder.tagMap.copy()
|
||||||
|
tagMap.update({
|
||||||
|
univ.Boolean.tagSet: BooleanEncoder(),
|
||||||
|
univ.Real.tagSet: RealEncoder(),
|
||||||
|
useful.GeneralizedTime.tagSet: GeneralizedTimeEncoder(),
|
||||||
|
useful.UTCTime.tagSet: UTCTimeEncoder(),
|
||||||
|
# Sequence & Set have same tags as SequenceOf & SetOf
|
||||||
|
univ.SetOf.tagSet: SetOfEncoder(),
|
||||||
|
univ.Sequence.typeId: SequenceEncoder()
|
||||||
|
})
|
||||||
|
|
||||||
|
typeMap = encoder.typeMap.copy()
|
||||||
|
typeMap.update({
|
||||||
|
univ.Boolean.typeId: BooleanEncoder(),
|
||||||
|
univ.Real.typeId: RealEncoder(),
|
||||||
|
useful.GeneralizedTime.typeId: GeneralizedTimeEncoder(),
|
||||||
|
useful.UTCTime.typeId: UTCTimeEncoder(),
|
||||||
|
# Sequence & Set have same tags as SequenceOf & SetOf
|
||||||
|
univ.Set.typeId: SetEncoder(),
|
||||||
|
univ.SetOf.typeId: SetOfEncoder(),
|
||||||
|
univ.Sequence.typeId: SequenceEncoder(),
|
||||||
|
univ.SequenceOf.typeId: SequenceOfEncoder()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class Encoder(encoder.Encoder):
|
||||||
|
fixedDefLengthMode = False
|
||||||
|
fixedChunkSize = 1000
|
||||||
|
|
||||||
|
#: Turns ASN.1 object into CER octet stream.
|
||||||
|
#:
|
||||||
|
#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
|
||||||
|
#: walks all its components recursively and produces a CER octet stream.
|
||||||
|
#:
|
||||||
|
#: Parameters
|
||||||
|
#: ----------
|
||||||
|
#: value: either a Python or pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
|
||||||
|
#: A Python or pyasn1 object to encode. If Python object is given, `asnSpec`
|
||||||
|
#: parameter is required to guide the encoding process.
|
||||||
|
#:
|
||||||
|
#: Keyword Args
|
||||||
|
#: ------------
|
||||||
|
#: asn1Spec:
|
||||||
|
#: Optional ASN.1 schema or value object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative
|
||||||
|
#:
|
||||||
|
#: Returns
|
||||||
|
#: -------
|
||||||
|
#: : :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2)
|
||||||
|
#: Given ASN.1 object encoded into BER octet-stream
|
||||||
|
#:
|
||||||
|
#: Raises
|
||||||
|
#: ------
|
||||||
|
#: ~pyasn1.error.PyAsn1Error
|
||||||
|
#: On encoding errors
|
||||||
|
#:
|
||||||
|
#: Examples
|
||||||
|
#: --------
|
||||||
|
#: Encode Python value into CER with ASN.1 schema
|
||||||
|
#:
|
||||||
|
#: .. code-block:: pycon
|
||||||
|
#:
|
||||||
|
#: >>> seq = SequenceOf(componentType=Integer())
|
||||||
|
#: >>> encode([1, 2, 3], asn1Spec=seq)
|
||||||
|
#: b'0\x80\x02\x01\x01\x02\x01\x02\x02\x01\x03\x00\x00'
|
||||||
|
#:
|
||||||
|
#: Encode ASN.1 value object into CER
|
||||||
|
#:
|
||||||
|
#: .. code-block:: pycon
|
||||||
|
#:
|
||||||
|
#: >>> seq = SequenceOf(componentType=Integer())
|
||||||
|
#: >>> seq.extend([1, 2, 3])
|
||||||
|
#: >>> encode(seq)
|
||||||
|
#: b'0\x80\x02\x01\x01\x02\x01\x02\x02\x01\x03\x00\x00'
|
||||||
|
#:
|
||||||
|
encode = Encoder(tagMap, typeMap)
|
||||||
|
|
||||||
|
# EncoderFactory queries class instance and builds a map of tags -> encoders
|
||||||
1
pyasn1/codec/der/__init__.py
Normal file
1
pyasn1/codec/der/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# This file is necessary to make this directory a package.
|
||||||
94
pyasn1/codec/der/decoder.py
Normal file
94
pyasn1/codec/der/decoder.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
from pyasn1.codec.cer import decoder
|
||||||
|
from pyasn1.type import univ
|
||||||
|
|
||||||
|
__all__ = ['decode']
|
||||||
|
|
||||||
|
|
||||||
|
class BitStringDecoder(decoder.BitStringDecoder):
|
||||||
|
supportConstructedForm = False
|
||||||
|
|
||||||
|
|
||||||
|
class OctetStringDecoder(decoder.OctetStringDecoder):
|
||||||
|
supportConstructedForm = False
|
||||||
|
|
||||||
|
# TODO: prohibit non-canonical encoding
|
||||||
|
RealDecoder = decoder.RealDecoder
|
||||||
|
|
||||||
|
tagMap = decoder.tagMap.copy()
|
||||||
|
tagMap.update(
|
||||||
|
{univ.BitString.tagSet: BitStringDecoder(),
|
||||||
|
univ.OctetString.tagSet: OctetStringDecoder(),
|
||||||
|
univ.Real.tagSet: RealDecoder()}
|
||||||
|
)
|
||||||
|
|
||||||
|
typeMap = decoder.typeMap.copy()
|
||||||
|
|
||||||
|
# Put in non-ambiguous types for faster codec lookup
|
||||||
|
for typeDecoder in tagMap.values():
|
||||||
|
if typeDecoder.protoComponent is not None:
|
||||||
|
typeId = typeDecoder.protoComponent.__class__.typeId
|
||||||
|
if typeId is not None and typeId not in typeMap:
|
||||||
|
typeMap[typeId] = typeDecoder
|
||||||
|
|
||||||
|
|
||||||
|
class Decoder(decoder.Decoder):
|
||||||
|
supportIndefLength = False
|
||||||
|
|
||||||
|
|
||||||
|
#: Turns DER octet stream into an ASN.1 object.
|
||||||
|
#:
|
||||||
|
#: Takes DER octet-stream and decode it into an ASN.1 object
|
||||||
|
#: (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which
|
||||||
|
#: may be a scalar or an arbitrary nested structure.
|
||||||
|
#:
|
||||||
|
#: Parameters
|
||||||
|
#: ----------
|
||||||
|
#: substrate: :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2)
|
||||||
|
#: DER octet-stream
|
||||||
|
#:
|
||||||
|
#: Keyword Args
|
||||||
|
#: ------------
|
||||||
|
#: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative
|
||||||
|
#: A pyasn1 type object to act as a template guiding the decoder. Depending on the ASN.1 structure
|
||||||
|
#: being decoded, *asn1Spec* may or may not be required. Most common reason for
|
||||||
|
#: it to require is that ASN.1 structure is encoded in *IMPLICIT* tagging mode.
|
||||||
|
#:
|
||||||
|
#: Returns
|
||||||
|
#: -------
|
||||||
|
#: : :py:class:`tuple`
|
||||||
|
#: A tuple of pyasn1 object recovered from DER substrate (:py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
|
||||||
|
#: and the unprocessed trailing portion of the *substrate* (may be empty)
|
||||||
|
#:
|
||||||
|
#: Raises
|
||||||
|
#: ------
|
||||||
|
#: ~pyasn1.error.PyAsn1Error, ~pyasn1.error.SubstrateUnderrunError
|
||||||
|
#: On decoding errors
|
||||||
|
#:
|
||||||
|
#: Examples
|
||||||
|
#: --------
|
||||||
|
#: Decode DER serialisation without ASN.1 schema
|
||||||
|
#:
|
||||||
|
#: .. code-block:: pycon
|
||||||
|
#:
|
||||||
|
#: >>> s, _ = decode(b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03')
|
||||||
|
#: >>> str(s)
|
||||||
|
#: SequenceOf:
|
||||||
|
#: 1 2 3
|
||||||
|
#:
|
||||||
|
#: Decode DER serialisation with ASN.1 schema
|
||||||
|
#:
|
||||||
|
#: .. code-block:: pycon
|
||||||
|
#:
|
||||||
|
#: >>> seq = SequenceOf(componentType=Integer())
|
||||||
|
#: >>> s, _ = decode(b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03', asn1Spec=seq)
|
||||||
|
#: >>> str(s)
|
||||||
|
#: SequenceOf:
|
||||||
|
#: 1 2 3
|
||||||
|
#:
|
||||||
|
decode = Decoder(tagMap, typeMap)
|
||||||
107
pyasn1/codec/der/encoder.py
Normal file
107
pyasn1/codec/der/encoder.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
from pyasn1 import error
|
||||||
|
from pyasn1.codec.cer import encoder
|
||||||
|
from pyasn1.type import univ
|
||||||
|
|
||||||
|
__all__ = ['encode']
|
||||||
|
|
||||||
|
|
||||||
|
class SetEncoder(encoder.SetEncoder):
|
||||||
|
@staticmethod
|
||||||
|
def _componentSortKey(componentAndType):
|
||||||
|
"""Sort SET components by tag
|
||||||
|
|
||||||
|
Sort depending on the actual Choice value (dynamic sort)
|
||||||
|
"""
|
||||||
|
component, asn1Spec = componentAndType
|
||||||
|
|
||||||
|
if asn1Spec is None:
|
||||||
|
compType = component
|
||||||
|
else:
|
||||||
|
compType = asn1Spec
|
||||||
|
|
||||||
|
if compType.typeId == univ.Choice.typeId and not compType.tagSet:
|
||||||
|
if asn1Spec is None:
|
||||||
|
return component.getComponent().tagSet
|
||||||
|
else:
|
||||||
|
# TODO: move out of sorting key function
|
||||||
|
names = [namedType.name for namedType in asn1Spec.componentType.namedTypes
|
||||||
|
if namedType.name in component]
|
||||||
|
if len(names) != 1:
|
||||||
|
raise error.PyAsn1Error(
|
||||||
|
'%s components for Choice at %r' % (len(names) and 'Multiple ' or 'None ', component))
|
||||||
|
|
||||||
|
# TODO: support nested CHOICE ordering
|
||||||
|
return asn1Spec[names[0]].tagSet
|
||||||
|
|
||||||
|
else:
|
||||||
|
return compType.tagSet
|
||||||
|
|
||||||
|
tagMap = encoder.tagMap.copy()
|
||||||
|
tagMap.update({
|
||||||
|
# Set & SetOf have same tags
|
||||||
|
univ.Set.tagSet: SetEncoder()
|
||||||
|
})
|
||||||
|
|
||||||
|
typeMap = encoder.typeMap.copy()
|
||||||
|
typeMap.update({
|
||||||
|
# Set & SetOf have same tags
|
||||||
|
univ.Set.typeId: SetEncoder()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class Encoder(encoder.Encoder):
|
||||||
|
fixedDefLengthMode = True
|
||||||
|
fixedChunkSize = 0
|
||||||
|
|
||||||
|
#: Turns ASN.1 object into DER octet stream.
|
||||||
|
#:
|
||||||
|
#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
|
||||||
|
#: walks all its components recursively and produces a DER octet stream.
|
||||||
|
#:
|
||||||
|
#: Parameters
|
||||||
|
#: ----------
|
||||||
|
#: value: either a Python or pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
|
||||||
|
#: A Python or pyasn1 object to encode. If Python object is given, `asnSpec`
|
||||||
|
#: parameter is required to guide the encoding process.
|
||||||
|
#:
|
||||||
|
#: Keyword Args
|
||||||
|
#: ------------
|
||||||
|
#: asn1Spec:
|
||||||
|
#: Optional ASN.1 schema or value object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative
|
||||||
|
#:
|
||||||
|
#: Returns
|
||||||
|
#: -------
|
||||||
|
#: : :py:class:`bytes` (Python 3) or :py:class:`str` (Python 2)
|
||||||
|
#: Given ASN.1 object encoded into BER octet-stream
|
||||||
|
#:
|
||||||
|
#: Raises
|
||||||
|
#: ------
|
||||||
|
#: ~pyasn1.error.PyAsn1Error
|
||||||
|
#: On encoding errors
|
||||||
|
#:
|
||||||
|
#: Examples
|
||||||
|
#: --------
|
||||||
|
#: Encode Python value into DER with ASN.1 schema
|
||||||
|
#:
|
||||||
|
#: .. code-block:: pycon
|
||||||
|
#:
|
||||||
|
#: >>> seq = SequenceOf(componentType=Integer())
|
||||||
|
#: >>> encode([1, 2, 3], asn1Spec=seq)
|
||||||
|
#: b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03'
|
||||||
|
#:
|
||||||
|
#: Encode ASN.1 value object into DER
|
||||||
|
#:
|
||||||
|
#: .. code-block:: pycon
|
||||||
|
#:
|
||||||
|
#: >>> seq = SequenceOf(componentType=Integer())
|
||||||
|
#: >>> seq.extend([1, 2, 3])
|
||||||
|
#: >>> encode(seq)
|
||||||
|
#: b'0\t\x02\x01\x01\x02\x01\x02\x02\x01\x03'
|
||||||
|
#:
|
||||||
|
encode = Encoder(tagMap, typeMap)
|
||||||
1
pyasn1/codec/native/__init__.py
Normal file
1
pyasn1/codec/native/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# This file is necessary to make this directory a package.
|
||||||
213
pyasn1/codec/native/decoder.py
Normal file
213
pyasn1/codec/native/decoder.py
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
from pyasn1 import debug
|
||||||
|
from pyasn1 import error
|
||||||
|
from pyasn1.type import base
|
||||||
|
from pyasn1.type import char
|
||||||
|
from pyasn1.type import tag
|
||||||
|
from pyasn1.type import univ
|
||||||
|
from pyasn1.type import useful
|
||||||
|
|
||||||
|
__all__ = ['decode']
|
||||||
|
|
||||||
|
LOG = debug.registerLoggee(__name__, flags=debug.DEBUG_DECODER)
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractScalarDecoder(object):
|
||||||
|
def __call__(self, pyObject, asn1Spec, decodeFun=None, **options):
|
||||||
|
return asn1Spec.clone(pyObject)
|
||||||
|
|
||||||
|
|
||||||
|
class BitStringDecoder(AbstractScalarDecoder):
|
||||||
|
def __call__(self, pyObject, asn1Spec, decodeFun=None, **options):
|
||||||
|
return asn1Spec.clone(univ.BitString.fromBinaryString(pyObject))
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceOrSetDecoder(object):
|
||||||
|
def __call__(self, pyObject, asn1Spec, decodeFun=None, **options):
|
||||||
|
asn1Value = asn1Spec.clone()
|
||||||
|
|
||||||
|
componentsTypes = asn1Spec.componentType
|
||||||
|
|
||||||
|
for field in asn1Value:
|
||||||
|
if field in pyObject:
|
||||||
|
asn1Value[field] = decodeFun(pyObject[field], componentsTypes[field].asn1Object, **options)
|
||||||
|
|
||||||
|
return asn1Value
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceOfOrSetOfDecoder(object):
|
||||||
|
def __call__(self, pyObject, asn1Spec, decodeFun=None, **options):
|
||||||
|
asn1Value = asn1Spec.clone()
|
||||||
|
|
||||||
|
for pyValue in pyObject:
|
||||||
|
asn1Value.append(decodeFun(pyValue, asn1Spec.componentType), **options)
|
||||||
|
|
||||||
|
return asn1Value
|
||||||
|
|
||||||
|
|
||||||
|
class ChoiceDecoder(object):
|
||||||
|
def __call__(self, pyObject, asn1Spec, decodeFun=None, **options):
|
||||||
|
asn1Value = asn1Spec.clone()
|
||||||
|
|
||||||
|
componentsTypes = asn1Spec.componentType
|
||||||
|
|
||||||
|
for field in pyObject:
|
||||||
|
if field in componentsTypes:
|
||||||
|
asn1Value[field] = decodeFun(pyObject[field], componentsTypes[field].asn1Object, **options)
|
||||||
|
break
|
||||||
|
|
||||||
|
return asn1Value
|
||||||
|
|
||||||
|
|
||||||
|
tagMap = {
|
||||||
|
univ.Integer.tagSet: AbstractScalarDecoder(),
|
||||||
|
univ.Boolean.tagSet: AbstractScalarDecoder(),
|
||||||
|
univ.BitString.tagSet: BitStringDecoder(),
|
||||||
|
univ.OctetString.tagSet: AbstractScalarDecoder(),
|
||||||
|
univ.Null.tagSet: AbstractScalarDecoder(),
|
||||||
|
univ.ObjectIdentifier.tagSet: AbstractScalarDecoder(),
|
||||||
|
univ.Enumerated.tagSet: AbstractScalarDecoder(),
|
||||||
|
univ.Real.tagSet: AbstractScalarDecoder(),
|
||||||
|
univ.Sequence.tagSet: SequenceOrSetDecoder(), # conflicts with SequenceOf
|
||||||
|
univ.Set.tagSet: SequenceOrSetDecoder(), # conflicts with SetOf
|
||||||
|
univ.Choice.tagSet: ChoiceDecoder(), # conflicts with Any
|
||||||
|
# character string types
|
||||||
|
char.UTF8String.tagSet: AbstractScalarDecoder(),
|
||||||
|
char.NumericString.tagSet: AbstractScalarDecoder(),
|
||||||
|
char.PrintableString.tagSet: AbstractScalarDecoder(),
|
||||||
|
char.TeletexString.tagSet: AbstractScalarDecoder(),
|
||||||
|
char.VideotexString.tagSet: AbstractScalarDecoder(),
|
||||||
|
char.IA5String.tagSet: AbstractScalarDecoder(),
|
||||||
|
char.GraphicString.tagSet: AbstractScalarDecoder(),
|
||||||
|
char.VisibleString.tagSet: AbstractScalarDecoder(),
|
||||||
|
char.GeneralString.tagSet: AbstractScalarDecoder(),
|
||||||
|
char.UniversalString.tagSet: AbstractScalarDecoder(),
|
||||||
|
char.BMPString.tagSet: AbstractScalarDecoder(),
|
||||||
|
# useful types
|
||||||
|
useful.ObjectDescriptor.tagSet: AbstractScalarDecoder(),
|
||||||
|
useful.GeneralizedTime.tagSet: AbstractScalarDecoder(),
|
||||||
|
useful.UTCTime.tagSet: AbstractScalarDecoder()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Put in ambiguous & non-ambiguous types for faster codec lookup
|
||||||
|
typeMap = {
|
||||||
|
univ.Integer.typeId: AbstractScalarDecoder(),
|
||||||
|
univ.Boolean.typeId: AbstractScalarDecoder(),
|
||||||
|
univ.BitString.typeId: BitStringDecoder(),
|
||||||
|
univ.OctetString.typeId: AbstractScalarDecoder(),
|
||||||
|
univ.Null.typeId: AbstractScalarDecoder(),
|
||||||
|
univ.ObjectIdentifier.typeId: AbstractScalarDecoder(),
|
||||||
|
univ.Enumerated.typeId: AbstractScalarDecoder(),
|
||||||
|
univ.Real.typeId: AbstractScalarDecoder(),
|
||||||
|
# ambiguous base types
|
||||||
|
univ.Set.typeId: SequenceOrSetDecoder(),
|
||||||
|
univ.SetOf.typeId: SequenceOfOrSetOfDecoder(),
|
||||||
|
univ.Sequence.typeId: SequenceOrSetDecoder(),
|
||||||
|
univ.SequenceOf.typeId: SequenceOfOrSetOfDecoder(),
|
||||||
|
univ.Choice.typeId: ChoiceDecoder(),
|
||||||
|
univ.Any.typeId: AbstractScalarDecoder(),
|
||||||
|
# character string types
|
||||||
|
char.UTF8String.typeId: AbstractScalarDecoder(),
|
||||||
|
char.NumericString.typeId: AbstractScalarDecoder(),
|
||||||
|
char.PrintableString.typeId: AbstractScalarDecoder(),
|
||||||
|
char.TeletexString.typeId: AbstractScalarDecoder(),
|
||||||
|
char.VideotexString.typeId: AbstractScalarDecoder(),
|
||||||
|
char.IA5String.typeId: AbstractScalarDecoder(),
|
||||||
|
char.GraphicString.typeId: AbstractScalarDecoder(),
|
||||||
|
char.VisibleString.typeId: AbstractScalarDecoder(),
|
||||||
|
char.GeneralString.typeId: AbstractScalarDecoder(),
|
||||||
|
char.UniversalString.typeId: AbstractScalarDecoder(),
|
||||||
|
char.BMPString.typeId: AbstractScalarDecoder(),
|
||||||
|
# useful types
|
||||||
|
useful.ObjectDescriptor.typeId: AbstractScalarDecoder(),
|
||||||
|
useful.GeneralizedTime.typeId: AbstractScalarDecoder(),
|
||||||
|
useful.UTCTime.typeId: AbstractScalarDecoder()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Decoder(object):
|
||||||
|
|
||||||
|
# noinspection PyDefaultArgument
|
||||||
|
def __init__(self, tagMap, typeMap):
|
||||||
|
self.__tagMap = tagMap
|
||||||
|
self.__typeMap = typeMap
|
||||||
|
|
||||||
|
def __call__(self, pyObject, asn1Spec, **options):
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
debug.scope.push(type(pyObject).__name__)
|
||||||
|
LOG('decoder called at scope %s, working with type %s' % (debug.scope, type(pyObject).__name__))
|
||||||
|
|
||||||
|
if asn1Spec is None or not isinstance(asn1Spec, base.Asn1Item):
|
||||||
|
raise error.PyAsn1Error('asn1Spec is not valid (should be an instance of an ASN.1 Item, not %s)' % asn1Spec.__class__.__name__)
|
||||||
|
|
||||||
|
try:
|
||||||
|
valueDecoder = self.__typeMap[asn1Spec.typeId]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
# use base type for codec lookup to recover untagged types
|
||||||
|
baseTagSet = tag.TagSet(asn1Spec.tagSet.baseTag, asn1Spec.tagSet.baseTag)
|
||||||
|
|
||||||
|
try:
|
||||||
|
valueDecoder = self.__tagMap[baseTagSet]
|
||||||
|
except KeyError:
|
||||||
|
raise error.PyAsn1Error('Unknown ASN.1 tag %s' % asn1Spec.tagSet)
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('calling decoder %s on Python type %s <%s>' % (type(valueDecoder).__name__, type(pyObject).__name__, repr(pyObject)))
|
||||||
|
|
||||||
|
value = valueDecoder(pyObject, asn1Spec, self, **options)
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('decoder %s produced ASN.1 type %s <%s>' % (type(valueDecoder).__name__, type(value).__name__, repr(value)))
|
||||||
|
debug.scope.pop()
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
#: Turns Python objects of built-in types into ASN.1 objects.
|
||||||
|
#:
|
||||||
|
#: Takes Python objects of built-in types and turns them into a tree of
|
||||||
|
#: ASN.1 objects (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative) which
|
||||||
|
#: may be a scalar or an arbitrary nested structure.
|
||||||
|
#:
|
||||||
|
#: Parameters
|
||||||
|
#: ----------
|
||||||
|
#: pyObject: :py:class:`object`
|
||||||
|
#: A scalar or nested Python objects
|
||||||
|
#:
|
||||||
|
#: Keyword Args
|
||||||
|
#: ------------
|
||||||
|
#: asn1Spec: any pyasn1 type object e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative
|
||||||
|
#: A pyasn1 type object to act as a template guiding the decoder. It is required
|
||||||
|
#: for successful interpretation of Python objects mapping into their ASN.1
|
||||||
|
#: representations.
|
||||||
|
#:
|
||||||
|
#: Returns
|
||||||
|
#: -------
|
||||||
|
#: : :py:class:`~pyasn1.type.base.PyAsn1Item` derivative
|
||||||
|
#: A scalar or constructed pyasn1 object
|
||||||
|
#:
|
||||||
|
#: Raises
|
||||||
|
#: ------
|
||||||
|
#: ~pyasn1.error.PyAsn1Error
|
||||||
|
#: On decoding errors
|
||||||
|
#:
|
||||||
|
#: Examples
|
||||||
|
#: --------
|
||||||
|
#: Decode native Python object into ASN.1 objects with ASN.1 schema
|
||||||
|
#:
|
||||||
|
#: .. code-block:: pycon
|
||||||
|
#:
|
||||||
|
#: >>> seq = SequenceOf(componentType=Integer())
|
||||||
|
#: >>> s, _ = decode([1, 2, 3], asn1Spec=seq)
|
||||||
|
#: >>> str(s)
|
||||||
|
#: SequenceOf:
|
||||||
|
#: 1 2 3
|
||||||
|
#:
|
||||||
|
decode = Decoder(tagMap, typeMap)
|
||||||
256
pyasn1/codec/native/encoder.py
Normal file
256
pyasn1/codec/native/encoder.py
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
try:
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
OrderedDict = dict
|
||||||
|
|
||||||
|
from pyasn1 import debug
|
||||||
|
from pyasn1 import error
|
||||||
|
from pyasn1.type import base
|
||||||
|
from pyasn1.type import char
|
||||||
|
from pyasn1.type import tag
|
||||||
|
from pyasn1.type import univ
|
||||||
|
from pyasn1.type import useful
|
||||||
|
|
||||||
|
__all__ = ['encode']
|
||||||
|
|
||||||
|
LOG = debug.registerLoggee(__name__, flags=debug.DEBUG_ENCODER)
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractItemEncoder(object):
|
||||||
|
def encode(self, value, encodeFun, **options):
|
||||||
|
raise error.PyAsn1Error('Not implemented')
|
||||||
|
|
||||||
|
|
||||||
|
class BooleanEncoder(AbstractItemEncoder):
|
||||||
|
def encode(self, value, encodeFun, **options):
|
||||||
|
return bool(value)
|
||||||
|
|
||||||
|
|
||||||
|
class IntegerEncoder(AbstractItemEncoder):
|
||||||
|
def encode(self, value, encodeFun, **options):
|
||||||
|
return int(value)
|
||||||
|
|
||||||
|
|
||||||
|
class BitStringEncoder(AbstractItemEncoder):
|
||||||
|
def encode(self, value, encodeFun, **options):
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
class OctetStringEncoder(AbstractItemEncoder):
|
||||||
|
def encode(self, value, encodeFun, **options):
|
||||||
|
return value.asOctets()
|
||||||
|
|
||||||
|
|
||||||
|
class TextStringEncoder(AbstractItemEncoder):
|
||||||
|
def encode(self, value, encodeFun, **options):
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
class NullEncoder(AbstractItemEncoder):
|
||||||
|
def encode(self, value, encodeFun, **options):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectIdentifierEncoder(AbstractItemEncoder):
|
||||||
|
def encode(self, value, encodeFun, **options):
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
class RealEncoder(AbstractItemEncoder):
|
||||||
|
def encode(self, value, encodeFun, **options):
|
||||||
|
return float(value)
|
||||||
|
|
||||||
|
|
||||||
|
class SetEncoder(AbstractItemEncoder):
|
||||||
|
protoDict = dict
|
||||||
|
|
||||||
|
def encode(self, value, encodeFun, **options):
|
||||||
|
inconsistency = value.isInconsistent
|
||||||
|
if inconsistency:
|
||||||
|
raise inconsistency
|
||||||
|
|
||||||
|
namedTypes = value.componentType
|
||||||
|
substrate = self.protoDict()
|
||||||
|
|
||||||
|
for idx, (key, subValue) in enumerate(value.items()):
|
||||||
|
if namedTypes and namedTypes[idx].isOptional and not value[idx].isValue:
|
||||||
|
continue
|
||||||
|
substrate[key] = encodeFun(subValue, **options)
|
||||||
|
return substrate
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceEncoder(SetEncoder):
|
||||||
|
protoDict = OrderedDict
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceOfEncoder(AbstractItemEncoder):
|
||||||
|
def encode(self, value, encodeFun, **options):
|
||||||
|
inconsistency = value.isInconsistent
|
||||||
|
if inconsistency:
|
||||||
|
raise inconsistency
|
||||||
|
return [encodeFun(x, **options) for x in value]
|
||||||
|
|
||||||
|
|
||||||
|
class ChoiceEncoder(SequenceEncoder):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AnyEncoder(AbstractItemEncoder):
|
||||||
|
def encode(self, value, encodeFun, **options):
|
||||||
|
return value.asOctets()
|
||||||
|
|
||||||
|
|
||||||
|
tagMap = {
|
||||||
|
univ.Boolean.tagSet: BooleanEncoder(),
|
||||||
|
univ.Integer.tagSet: IntegerEncoder(),
|
||||||
|
univ.BitString.tagSet: BitStringEncoder(),
|
||||||
|
univ.OctetString.tagSet: OctetStringEncoder(),
|
||||||
|
univ.Null.tagSet: NullEncoder(),
|
||||||
|
univ.ObjectIdentifier.tagSet: ObjectIdentifierEncoder(),
|
||||||
|
univ.Enumerated.tagSet: IntegerEncoder(),
|
||||||
|
univ.Real.tagSet: RealEncoder(),
|
||||||
|
# Sequence & Set have same tags as SequenceOf & SetOf
|
||||||
|
univ.SequenceOf.tagSet: SequenceOfEncoder(),
|
||||||
|
univ.SetOf.tagSet: SequenceOfEncoder(),
|
||||||
|
univ.Choice.tagSet: ChoiceEncoder(),
|
||||||
|
# character string types
|
||||||
|
char.UTF8String.tagSet: TextStringEncoder(),
|
||||||
|
char.NumericString.tagSet: TextStringEncoder(),
|
||||||
|
char.PrintableString.tagSet: TextStringEncoder(),
|
||||||
|
char.TeletexString.tagSet: TextStringEncoder(),
|
||||||
|
char.VideotexString.tagSet: TextStringEncoder(),
|
||||||
|
char.IA5String.tagSet: TextStringEncoder(),
|
||||||
|
char.GraphicString.tagSet: TextStringEncoder(),
|
||||||
|
char.VisibleString.tagSet: TextStringEncoder(),
|
||||||
|
char.GeneralString.tagSet: TextStringEncoder(),
|
||||||
|
char.UniversalString.tagSet: TextStringEncoder(),
|
||||||
|
char.BMPString.tagSet: TextStringEncoder(),
|
||||||
|
# useful types
|
||||||
|
useful.ObjectDescriptor.tagSet: OctetStringEncoder(),
|
||||||
|
useful.GeneralizedTime.tagSet: OctetStringEncoder(),
|
||||||
|
useful.UTCTime.tagSet: OctetStringEncoder()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Put in ambiguous & non-ambiguous types for faster codec lookup
|
||||||
|
typeMap = {
|
||||||
|
univ.Boolean.typeId: BooleanEncoder(),
|
||||||
|
univ.Integer.typeId: IntegerEncoder(),
|
||||||
|
univ.BitString.typeId: BitStringEncoder(),
|
||||||
|
univ.OctetString.typeId: OctetStringEncoder(),
|
||||||
|
univ.Null.typeId: NullEncoder(),
|
||||||
|
univ.ObjectIdentifier.typeId: ObjectIdentifierEncoder(),
|
||||||
|
univ.Enumerated.typeId: IntegerEncoder(),
|
||||||
|
univ.Real.typeId: RealEncoder(),
|
||||||
|
# Sequence & Set have same tags as SequenceOf & SetOf
|
||||||
|
univ.Set.typeId: SetEncoder(),
|
||||||
|
univ.SetOf.typeId: SequenceOfEncoder(),
|
||||||
|
univ.Sequence.typeId: SequenceEncoder(),
|
||||||
|
univ.SequenceOf.typeId: SequenceOfEncoder(),
|
||||||
|
univ.Choice.typeId: ChoiceEncoder(),
|
||||||
|
univ.Any.typeId: AnyEncoder(),
|
||||||
|
# character string types
|
||||||
|
char.UTF8String.typeId: OctetStringEncoder(),
|
||||||
|
char.NumericString.typeId: OctetStringEncoder(),
|
||||||
|
char.PrintableString.typeId: OctetStringEncoder(),
|
||||||
|
char.TeletexString.typeId: OctetStringEncoder(),
|
||||||
|
char.VideotexString.typeId: OctetStringEncoder(),
|
||||||
|
char.IA5String.typeId: OctetStringEncoder(),
|
||||||
|
char.GraphicString.typeId: OctetStringEncoder(),
|
||||||
|
char.VisibleString.typeId: OctetStringEncoder(),
|
||||||
|
char.GeneralString.typeId: OctetStringEncoder(),
|
||||||
|
char.UniversalString.typeId: OctetStringEncoder(),
|
||||||
|
char.BMPString.typeId: OctetStringEncoder(),
|
||||||
|
# useful types
|
||||||
|
useful.ObjectDescriptor.typeId: OctetStringEncoder(),
|
||||||
|
useful.GeneralizedTime.typeId: OctetStringEncoder(),
|
||||||
|
useful.UTCTime.typeId: OctetStringEncoder()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Encoder(object):
|
||||||
|
|
||||||
|
# noinspection PyDefaultArgument
|
||||||
|
def __init__(self, tagMap, typeMap={}):
|
||||||
|
self.__tagMap = tagMap
|
||||||
|
self.__typeMap = typeMap
|
||||||
|
|
||||||
|
def __call__(self, value, **options):
|
||||||
|
if not isinstance(value, base.Asn1Item):
|
||||||
|
raise error.PyAsn1Error('value is not valid (should be an instance of an ASN.1 Item)')
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
debug.scope.push(type(value).__name__)
|
||||||
|
LOG('encoder called for type %s <%s>' % (type(value).__name__, value.prettyPrint()))
|
||||||
|
|
||||||
|
tagSet = value.tagSet
|
||||||
|
|
||||||
|
try:
|
||||||
|
concreteEncoder = self.__typeMap[value.typeId]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
# use base type for codec lookup to recover untagged types
|
||||||
|
baseTagSet = tag.TagSet(value.tagSet.baseTag, value.tagSet.baseTag)
|
||||||
|
|
||||||
|
try:
|
||||||
|
concreteEncoder = self.__tagMap[baseTagSet]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
raise error.PyAsn1Error('No encoder for %s' % (value,))
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('using value codec %s chosen by %s' % (concreteEncoder.__class__.__name__, tagSet))
|
||||||
|
|
||||||
|
pyObject = concreteEncoder.encode(value, self, **options)
|
||||||
|
|
||||||
|
if LOG:
|
||||||
|
LOG('encoder %s produced: %s' % (type(concreteEncoder).__name__, repr(pyObject)))
|
||||||
|
debug.scope.pop()
|
||||||
|
|
||||||
|
return pyObject
|
||||||
|
|
||||||
|
|
||||||
|
#: Turns ASN.1 object into a Python built-in type object(s).
|
||||||
|
#:
|
||||||
|
#: Takes any ASN.1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
|
||||||
|
#: walks all its components recursively and produces a Python built-in type or a tree
|
||||||
|
#: of those.
|
||||||
|
#:
|
||||||
|
#: One exception is that instead of :py:class:`dict`, the :py:class:`OrderedDict`
|
||||||
|
#: can be produced (whenever available) to preserve ordering of the components
|
||||||
|
#: in ASN.1 SEQUENCE.
|
||||||
|
#:
|
||||||
|
#: Parameters
|
||||||
|
#: ----------
|
||||||
|
# asn1Value: any pyasn1 object (e.g. :py:class:`~pyasn1.type.base.PyAsn1Item` derivative)
|
||||||
|
#: pyasn1 object to encode (or a tree of them)
|
||||||
|
#:
|
||||||
|
#: Returns
|
||||||
|
#: -------
|
||||||
|
#: : :py:class:`object`
|
||||||
|
#: Python built-in type instance (or a tree of them)
|
||||||
|
#:
|
||||||
|
#: Raises
|
||||||
|
#: ------
|
||||||
|
#: ~pyasn1.error.PyAsn1Error
|
||||||
|
#: On encoding errors
|
||||||
|
#:
|
||||||
|
#: Examples
|
||||||
|
#: --------
|
||||||
|
#: Encode ASN.1 value object into native Python types
|
||||||
|
#:
|
||||||
|
#: .. code-block:: pycon
|
||||||
|
#:
|
||||||
|
#: >>> seq = SequenceOf(componentType=Integer())
|
||||||
|
#: >>> seq.extend([1, 2, 3])
|
||||||
|
#: >>> encode(seq)
|
||||||
|
#: [1, 2, 3]
|
||||||
|
#:
|
||||||
|
encode = Encoder(tagMap, typeMap)
|
||||||
1
pyasn1/compat/__init__.py
Normal file
1
pyasn1/compat/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# This file is necessary to make this directory a package.
|
||||||
33
pyasn1/compat/binary.py
Normal file
33
pyasn1/compat/binary.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
from sys import version_info
|
||||||
|
|
||||||
|
if version_info[0:2] < (2, 6):
|
||||||
|
def bin(value):
|
||||||
|
bitstring = []
|
||||||
|
|
||||||
|
if value > 0:
|
||||||
|
prefix = '0b'
|
||||||
|
elif value < 0:
|
||||||
|
prefix = '-0b'
|
||||||
|
value = abs(value)
|
||||||
|
else:
|
||||||
|
prefix = '0b0'
|
||||||
|
|
||||||
|
while value:
|
||||||
|
if value & 1 == 1:
|
||||||
|
bitstring.append('1')
|
||||||
|
else:
|
||||||
|
bitstring.append('0')
|
||||||
|
|
||||||
|
value >>= 1
|
||||||
|
|
||||||
|
bitstring.reverse()
|
||||||
|
|
||||||
|
return prefix + ''.join(bitstring)
|
||||||
|
else:
|
||||||
|
bin = bin
|
||||||
20
pyasn1/compat/calling.py
Normal file
20
pyasn1/compat/calling.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
from sys import version_info
|
||||||
|
|
||||||
|
__all__ = ['callable']
|
||||||
|
|
||||||
|
|
||||||
|
if (2, 7) < version_info[:2] < (3, 2):
|
||||||
|
import collections
|
||||||
|
|
||||||
|
def callable(x):
|
||||||
|
return isinstance(x, collections.Callable)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
callable = callable
|
||||||
22
pyasn1/compat/dateandtime.py
Normal file
22
pyasn1/compat/dateandtime.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
from sys import version_info
|
||||||
|
|
||||||
|
__all__ = ['strptime']
|
||||||
|
|
||||||
|
|
||||||
|
if version_info[:2] <= (2, 4):
|
||||||
|
|
||||||
|
def strptime(text, dateFormat):
|
||||||
|
return datetime(*(time.strptime(text, dateFormat)[0:6]))
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def strptime(text, dateFormat):
|
||||||
|
return datetime.strptime(text, dateFormat)
|
||||||
110
pyasn1/compat/integer.py
Normal file
110
pyasn1/compat/integer.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
import platform
|
||||||
|
|
||||||
|
implementation = platform.python_implementation()
|
||||||
|
|
||||||
|
except (ImportError, AttributeError):
|
||||||
|
implementation = 'CPython'
|
||||||
|
|
||||||
|
from pyasn1.compat.octets import oct2int, null, ensureString
|
||||||
|
|
||||||
|
if sys.version_info[0:2] < (3, 2) or implementation != 'CPython':
|
||||||
|
from binascii import a2b_hex, b2a_hex
|
||||||
|
|
||||||
|
if sys.version_info[0] > 2:
|
||||||
|
long = int
|
||||||
|
|
||||||
|
def from_bytes(octets, signed=False):
|
||||||
|
if not octets:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
value = long(b2a_hex(ensureString(octets)), 16)
|
||||||
|
|
||||||
|
if signed and oct2int(octets[0]) & 0x80:
|
||||||
|
return value - (1 << len(octets) * 8)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def to_bytes(value, signed=False, length=0):
|
||||||
|
if value < 0:
|
||||||
|
if signed:
|
||||||
|
bits = bitLength(value)
|
||||||
|
|
||||||
|
# two's complement form
|
||||||
|
maxValue = 1 << bits
|
||||||
|
valueToEncode = (value + maxValue) % maxValue
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise OverflowError('can\'t convert negative int to unsigned')
|
||||||
|
elif value == 0 and length == 0:
|
||||||
|
return null
|
||||||
|
else:
|
||||||
|
bits = 0
|
||||||
|
valueToEncode = value
|
||||||
|
|
||||||
|
hexValue = hex(valueToEncode)[2:]
|
||||||
|
if hexValue.endswith('L'):
|
||||||
|
hexValue = hexValue[:-1]
|
||||||
|
|
||||||
|
if len(hexValue) & 1:
|
||||||
|
hexValue = '0' + hexValue
|
||||||
|
|
||||||
|
# padding may be needed for two's complement encoding
|
||||||
|
if value != valueToEncode or length:
|
||||||
|
hexLength = len(hexValue) * 4
|
||||||
|
|
||||||
|
padLength = max(length, bits)
|
||||||
|
|
||||||
|
if padLength > hexLength:
|
||||||
|
hexValue = '00' * ((padLength - hexLength - 1) // 8 + 1) + hexValue
|
||||||
|
elif length and hexLength - length > 7:
|
||||||
|
raise OverflowError('int too big to convert')
|
||||||
|
|
||||||
|
firstOctet = int(hexValue[:2], 16)
|
||||||
|
|
||||||
|
if signed:
|
||||||
|
if firstOctet & 0x80:
|
||||||
|
if value >= 0:
|
||||||
|
hexValue = '00' + hexValue
|
||||||
|
elif value < 0:
|
||||||
|
hexValue = 'ff' + hexValue
|
||||||
|
|
||||||
|
octets_value = a2b_hex(hexValue)
|
||||||
|
|
||||||
|
return octets_value
|
||||||
|
|
||||||
|
def bitLength(number):
|
||||||
|
# bits in unsigned number
|
||||||
|
hexValue = hex(abs(number))
|
||||||
|
bits = len(hexValue) - 2
|
||||||
|
if hexValue.endswith('L'):
|
||||||
|
bits -= 1
|
||||||
|
if bits & 1:
|
||||||
|
bits += 1
|
||||||
|
bits *= 4
|
||||||
|
# TODO: strip lhs zeros
|
||||||
|
return bits
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def from_bytes(octets, signed=False):
|
||||||
|
return int.from_bytes(bytes(octets), 'big', signed=signed)
|
||||||
|
|
||||||
|
def to_bytes(value, signed=False, length=0):
|
||||||
|
length = max(value.bit_length(), length)
|
||||||
|
|
||||||
|
if signed and length % 8 == 0:
|
||||||
|
length += 1
|
||||||
|
|
||||||
|
return value.to_bytes(length // 8 + (length % 8 and 1 or 0), 'big', signed=signed)
|
||||||
|
|
||||||
|
def bitLength(number):
|
||||||
|
return int(number).bit_length()
|
||||||
46
pyasn1/compat/octets.py
Normal file
46
pyasn1/compat/octets.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
from sys import version_info
|
||||||
|
|
||||||
|
if version_info[0] <= 2:
|
||||||
|
int2oct = chr
|
||||||
|
# noinspection PyPep8
|
||||||
|
ints2octs = lambda s: ''.join([int2oct(x) for x in s])
|
||||||
|
null = ''
|
||||||
|
oct2int = ord
|
||||||
|
# TODO: refactor to return a sequence of ints
|
||||||
|
# noinspection PyPep8
|
||||||
|
octs2ints = lambda s: [oct2int(x) for x in s]
|
||||||
|
# noinspection PyPep8
|
||||||
|
str2octs = lambda x: x
|
||||||
|
# noinspection PyPep8
|
||||||
|
octs2str = lambda x: x
|
||||||
|
# noinspection PyPep8
|
||||||
|
isOctetsType = lambda s: isinstance(s, str)
|
||||||
|
# noinspection PyPep8
|
||||||
|
isStringType = lambda s: isinstance(s, (str, unicode))
|
||||||
|
# noinspection PyPep8
|
||||||
|
ensureString = str
|
||||||
|
else:
|
||||||
|
ints2octs = bytes
|
||||||
|
# noinspection PyPep8
|
||||||
|
int2oct = lambda x: ints2octs((x,))
|
||||||
|
null = ints2octs()
|
||||||
|
# noinspection PyPep8
|
||||||
|
oct2int = lambda x: x
|
||||||
|
# noinspection PyPep8
|
||||||
|
octs2ints = lambda x: x
|
||||||
|
# noinspection PyPep8
|
||||||
|
str2octs = lambda x: x.encode('iso-8859-1')
|
||||||
|
# noinspection PyPep8
|
||||||
|
octs2str = lambda x: x.decode('iso-8859-1')
|
||||||
|
# noinspection PyPep8
|
||||||
|
isOctetsType = lambda s: isinstance(s, bytes)
|
||||||
|
# noinspection PyPep8
|
||||||
|
isStringType = lambda s: isinstance(s, str)
|
||||||
|
# noinspection PyPep8
|
||||||
|
ensureString = bytes
|
||||||
26
pyasn1/compat/string.py
Normal file
26
pyasn1/compat/string.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
from sys import version_info
|
||||||
|
|
||||||
|
if version_info[:2] <= (2, 5):
|
||||||
|
|
||||||
|
def partition(string, sep):
|
||||||
|
try:
|
||||||
|
a, c = string.split(sep, 1)
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
a, b, c = string, '', ''
|
||||||
|
|
||||||
|
else:
|
||||||
|
b = sep
|
||||||
|
|
||||||
|
return a, b, c
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def partition(string, sep):
|
||||||
|
return string.partition(sep)
|
||||||
157
pyasn1/debug.py
Normal file
157
pyasn1/debug.py
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from pyasn1 import __version__
|
||||||
|
from pyasn1 import error
|
||||||
|
from pyasn1.compat.octets import octs2ints
|
||||||
|
|
||||||
|
__all__ = ['Debug', 'setLogger', 'hexdump']
|
||||||
|
|
||||||
|
DEBUG_NONE = 0x0000
|
||||||
|
DEBUG_ENCODER = 0x0001
|
||||||
|
DEBUG_DECODER = 0x0002
|
||||||
|
DEBUG_ALL = 0xffff
|
||||||
|
|
||||||
|
FLAG_MAP = {
|
||||||
|
'none': DEBUG_NONE,
|
||||||
|
'encoder': DEBUG_ENCODER,
|
||||||
|
'decoder': DEBUG_DECODER,
|
||||||
|
'all': DEBUG_ALL
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGEE_MAP = {}
|
||||||
|
|
||||||
|
|
||||||
|
class Printer(object):
|
||||||
|
# noinspection PyShadowingNames
|
||||||
|
def __init__(self, logger=None, handler=None, formatter=None):
|
||||||
|
if logger is None:
|
||||||
|
logger = logging.getLogger('pyasn1')
|
||||||
|
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
if handler is None:
|
||||||
|
handler = logging.StreamHandler()
|
||||||
|
|
||||||
|
if formatter is None:
|
||||||
|
formatter = logging.Formatter('%(asctime)s %(name)s: %(message)s')
|
||||||
|
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
handler.setLevel(logging.DEBUG)
|
||||||
|
logger.addHandler(handler)
|
||||||
|
|
||||||
|
self.__logger = logger
|
||||||
|
|
||||||
|
def __call__(self, msg):
|
||||||
|
self.__logger.debug(msg)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '<python logging>'
|
||||||
|
|
||||||
|
|
||||||
|
if hasattr(logging, 'NullHandler'):
|
||||||
|
NullHandler = logging.NullHandler
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Python 2.6 and older
|
||||||
|
class NullHandler(logging.Handler):
|
||||||
|
def emit(self, record):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Debug(object):
|
||||||
|
defaultPrinter = Printer()
|
||||||
|
|
||||||
|
def __init__(self, *flags, **options):
|
||||||
|
self._flags = DEBUG_NONE
|
||||||
|
|
||||||
|
if 'loggerName' in options:
|
||||||
|
# route our logs to parent logger
|
||||||
|
self._printer = Printer(
|
||||||
|
logger=logging.getLogger(options['loggerName']),
|
||||||
|
handler=NullHandler()
|
||||||
|
)
|
||||||
|
|
||||||
|
elif 'printer' in options:
|
||||||
|
self._printer = options.get('printer')
|
||||||
|
|
||||||
|
else:
|
||||||
|
self._printer = self.defaultPrinter
|
||||||
|
|
||||||
|
self._printer('running pyasn1 %s, debug flags %s' % (__version__, ', '.join(flags)))
|
||||||
|
|
||||||
|
for flag in flags:
|
||||||
|
inverse = flag and flag[0] in ('!', '~')
|
||||||
|
if inverse:
|
||||||
|
flag = flag[1:]
|
||||||
|
try:
|
||||||
|
if inverse:
|
||||||
|
self._flags &= ~FLAG_MAP[flag]
|
||||||
|
else:
|
||||||
|
self._flags |= FLAG_MAP[flag]
|
||||||
|
except KeyError:
|
||||||
|
raise error.PyAsn1Error('bad debug flag %s' % flag)
|
||||||
|
|
||||||
|
self._printer("debug category '%s' %s" % (flag, inverse and 'disabled' or 'enabled'))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'logger %s, flags %x' % (self._printer, self._flags)
|
||||||
|
|
||||||
|
def __call__(self, msg):
|
||||||
|
self._printer(msg)
|
||||||
|
|
||||||
|
def __and__(self, flag):
|
||||||
|
return self._flags & flag
|
||||||
|
|
||||||
|
def __rand__(self, flag):
|
||||||
|
return flag & self._flags
|
||||||
|
|
||||||
|
_LOG = DEBUG_NONE
|
||||||
|
|
||||||
|
|
||||||
|
def setLogger(userLogger):
|
||||||
|
global _LOG
|
||||||
|
|
||||||
|
if userLogger:
|
||||||
|
_LOG = userLogger
|
||||||
|
else:
|
||||||
|
_LOG = DEBUG_NONE
|
||||||
|
|
||||||
|
# Update registered logging clients
|
||||||
|
for module, (name, flags) in LOGGEE_MAP.items():
|
||||||
|
setattr(module, name, _LOG & flags and _LOG or DEBUG_NONE)
|
||||||
|
|
||||||
|
|
||||||
|
def registerLoggee(module, name='LOG', flags=DEBUG_NONE):
|
||||||
|
LOGGEE_MAP[sys.modules[module]] = name, flags
|
||||||
|
setLogger(_LOG)
|
||||||
|
return _LOG
|
||||||
|
|
||||||
|
|
||||||
|
def hexdump(octets):
|
||||||
|
return ' '.join(
|
||||||
|
['%s%.2X' % (n % 16 == 0 and ('\n%.5d: ' % n) or '', x)
|
||||||
|
for n, x in zip(range(len(octets)), octs2ints(octets))]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Scope(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._list = []
|
||||||
|
|
||||||
|
def __str__(self): return '.'.join(self._list)
|
||||||
|
|
||||||
|
def push(self, token):
|
||||||
|
self._list.append(token)
|
||||||
|
|
||||||
|
def pop(self):
|
||||||
|
return self._list.pop()
|
||||||
|
|
||||||
|
|
||||||
|
scope = Scope()
|
||||||
75
pyasn1/error.py
Normal file
75
pyasn1/error.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
class PyAsn1Error(Exception):
|
||||||
|
"""Base pyasn1 exception
|
||||||
|
|
||||||
|
`PyAsn1Error` is the base exception class (based on
|
||||||
|
:class:`Exception`) that represents all possible ASN.1 related
|
||||||
|
errors.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ValueConstraintError(PyAsn1Error):
|
||||||
|
"""ASN.1 type constraints violation exception
|
||||||
|
|
||||||
|
The `ValueConstraintError` exception indicates an ASN.1 value
|
||||||
|
constraint violation.
|
||||||
|
|
||||||
|
It might happen on value object instantiation (for scalar types) or on
|
||||||
|
serialization (for constructed types).
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class SubstrateUnderrunError(PyAsn1Error):
|
||||||
|
"""ASN.1 data structure deserialization error
|
||||||
|
|
||||||
|
The `SubstrateUnderrunError` exception indicates insufficient serialised
|
||||||
|
data on input of a de-serialization codec.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class PyAsn1UnicodeError(PyAsn1Error, UnicodeError):
|
||||||
|
"""Unicode text processing error
|
||||||
|
|
||||||
|
The `PyAsn1UnicodeError` exception is a base class for errors relating to
|
||||||
|
unicode text de/serialization.
|
||||||
|
|
||||||
|
Apart from inheriting from :class:`PyAsn1Error`, it also inherits from
|
||||||
|
:class:`UnicodeError` to help the caller catching unicode-related errors.
|
||||||
|
"""
|
||||||
|
def __init__(self, message, unicode_error=None):
|
||||||
|
if isinstance(unicode_error, UnicodeError):
|
||||||
|
UnicodeError.__init__(self, *unicode_error.args)
|
||||||
|
PyAsn1Error.__init__(self, message)
|
||||||
|
|
||||||
|
|
||||||
|
class PyAsn1UnicodeDecodeError(PyAsn1UnicodeError, UnicodeDecodeError):
|
||||||
|
"""Unicode text decoding error
|
||||||
|
|
||||||
|
The `PyAsn1UnicodeDecodeError` exception represents a failure to
|
||||||
|
deserialize unicode text.
|
||||||
|
|
||||||
|
Apart from inheriting from :class:`PyAsn1UnicodeError`, it also inherits
|
||||||
|
from :class:`UnicodeDecodeError` to help the caller catching unicode-related
|
||||||
|
errors.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class PyAsn1UnicodeEncodeError(PyAsn1UnicodeError, UnicodeEncodeError):
|
||||||
|
"""Unicode text encoding error
|
||||||
|
|
||||||
|
The `PyAsn1UnicodeEncodeError` exception represents a failure to
|
||||||
|
serialize unicode text.
|
||||||
|
|
||||||
|
Apart from inheriting from :class:`PyAsn1UnicodeError`, it also inherits
|
||||||
|
from :class:`UnicodeEncodeError` to help the caller catching
|
||||||
|
unicode-related errors.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
1
pyasn1/type/__init__.py
Normal file
1
pyasn1/type/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# This file is necessary to make this directory a package.
|
||||||
707
pyasn1/type/base.py
Normal file
707
pyasn1/type/base.py
Normal file
@ -0,0 +1,707 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from pyasn1 import error
|
||||||
|
from pyasn1.compat import calling
|
||||||
|
from pyasn1.type import constraint
|
||||||
|
from pyasn1.type import tag
|
||||||
|
from pyasn1.type import tagmap
|
||||||
|
|
||||||
|
__all__ = ['Asn1Item', 'Asn1Type', 'SimpleAsn1Type',
|
||||||
|
'ConstructedAsn1Type']
|
||||||
|
|
||||||
|
|
||||||
|
class Asn1Item(object):
|
||||||
|
@classmethod
|
||||||
|
def getTypeId(cls, increment=1):
|
||||||
|
try:
|
||||||
|
Asn1Item._typeCounter += increment
|
||||||
|
except AttributeError:
|
||||||
|
Asn1Item._typeCounter = increment
|
||||||
|
return Asn1Item._typeCounter
|
||||||
|
|
||||||
|
|
||||||
|
class Asn1Type(Asn1Item):
|
||||||
|
"""Base class for all classes representing ASN.1 types.
|
||||||
|
|
||||||
|
In the user code, |ASN.1| class is normally used only for telling
|
||||||
|
ASN.1 objects from others.
|
||||||
|
|
||||||
|
Note
|
||||||
|
----
|
||||||
|
For as long as ASN.1 is concerned, a way to compare ASN.1 types
|
||||||
|
is to use :meth:`isSameTypeWith` and :meth:`isSuperTypeOf` methods.
|
||||||
|
"""
|
||||||
|
#: Set or return a :py:class:`~pyasn1.type.tag.TagSet` object representing
|
||||||
|
#: ASN.1 tag(s) associated with |ASN.1| type.
|
||||||
|
tagSet = tag.TagSet()
|
||||||
|
|
||||||
|
#: Default :py:class:`~pyasn1.type.constraint.ConstraintsIntersection`
|
||||||
|
#: object imposing constraints on initialization values.
|
||||||
|
subtypeSpec = constraint.ConstraintsIntersection()
|
||||||
|
|
||||||
|
# Disambiguation ASN.1 types identification
|
||||||
|
typeId = None
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
readOnly = {
|
||||||
|
'tagSet': self.tagSet,
|
||||||
|
'subtypeSpec': self.subtypeSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
readOnly.update(kwargs)
|
||||||
|
|
||||||
|
self.__dict__.update(readOnly)
|
||||||
|
|
||||||
|
self._readOnly = readOnly
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
if name[0] != '_' and name in self._readOnly:
|
||||||
|
raise error.PyAsn1Error('read-only instance attribute "%s"' % name)
|
||||||
|
|
||||||
|
self.__dict__[name] = value
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.prettyPrint()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def readOnly(self):
|
||||||
|
return self._readOnly
|
||||||
|
|
||||||
|
@property
|
||||||
|
def effectiveTagSet(self):
|
||||||
|
"""For |ASN.1| type is equivalent to *tagSet*
|
||||||
|
"""
|
||||||
|
return self.tagSet # used by untagged types
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tagMap(self):
|
||||||
|
"""Return a :class:`~pyasn1.type.tagmap.TagMap` object mapping ASN.1 tags to ASN.1 objects within callee object.
|
||||||
|
"""
|
||||||
|
return tagmap.TagMap({self.tagSet: self})
|
||||||
|
|
||||||
|
def isSameTypeWith(self, other, matchTags=True, matchConstraints=True):
|
||||||
|
"""Examine |ASN.1| type for equality with other ASN.1 type.
|
||||||
|
|
||||||
|
ASN.1 tags (:py:mod:`~pyasn1.type.tag`) and constraints
|
||||||
|
(:py:mod:`~pyasn1.type.constraint`) are examined when carrying
|
||||||
|
out ASN.1 types comparison.
|
||||||
|
|
||||||
|
Python class inheritance relationship is NOT considered.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
other: a pyasn1 type object
|
||||||
|
Class instance representing ASN.1 type.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :class:`bool`
|
||||||
|
:obj:`True` if *other* is |ASN.1| type,
|
||||||
|
:obj:`False` otherwise.
|
||||||
|
"""
|
||||||
|
return (self is other or
|
||||||
|
(not matchTags or self.tagSet == other.tagSet) and
|
||||||
|
(not matchConstraints or self.subtypeSpec == other.subtypeSpec))
|
||||||
|
|
||||||
|
def isSuperTypeOf(self, other, matchTags=True, matchConstraints=True):
|
||||||
|
"""Examine |ASN.1| type for subtype relationship with other ASN.1 type.
|
||||||
|
|
||||||
|
ASN.1 tags (:py:mod:`~pyasn1.type.tag`) and constraints
|
||||||
|
(:py:mod:`~pyasn1.type.constraint`) are examined when carrying
|
||||||
|
out ASN.1 types comparison.
|
||||||
|
|
||||||
|
Python class inheritance relationship is NOT considered.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
other: a pyasn1 type object
|
||||||
|
Class instance representing ASN.1 type.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :class:`bool`
|
||||||
|
:obj:`True` if *other* is a subtype of |ASN.1| type,
|
||||||
|
:obj:`False` otherwise.
|
||||||
|
"""
|
||||||
|
return (not matchTags or
|
||||||
|
(self.tagSet.isSuperTagSetOf(other.tagSet)) and
|
||||||
|
(not matchConstraints or self.subtypeSpec.isSuperTypeOf(other.subtypeSpec)))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def isNoValue(*values):
|
||||||
|
for value in values:
|
||||||
|
if value is not noValue:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def prettyPrint(self, scope=0):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
# backward compatibility
|
||||||
|
|
||||||
|
def getTagSet(self):
|
||||||
|
return self.tagSet
|
||||||
|
|
||||||
|
def getEffectiveTagSet(self):
|
||||||
|
return self.effectiveTagSet
|
||||||
|
|
||||||
|
def getTagMap(self):
|
||||||
|
return self.tagMap
|
||||||
|
|
||||||
|
def getSubtypeSpec(self):
|
||||||
|
return self.subtypeSpec
|
||||||
|
|
||||||
|
# backward compatibility
|
||||||
|
def hasValue(self):
|
||||||
|
return self.isValue
|
||||||
|
|
||||||
|
# Backward compatibility
|
||||||
|
Asn1ItemBase = Asn1Type
|
||||||
|
|
||||||
|
|
||||||
|
class NoValue(object):
|
||||||
|
"""Create a singleton instance of NoValue class.
|
||||||
|
|
||||||
|
The *NoValue* sentinel object represents an instance of ASN.1 schema
|
||||||
|
object as opposed to ASN.1 value object.
|
||||||
|
|
||||||
|
Only ASN.1 schema-related operations can be performed on ASN.1
|
||||||
|
schema objects.
|
||||||
|
|
||||||
|
Warning
|
||||||
|
-------
|
||||||
|
Any operation attempted on the *noValue* object will raise the
|
||||||
|
*PyAsn1Error* exception.
|
||||||
|
"""
|
||||||
|
skipMethods = set(
|
||||||
|
('__slots__',
|
||||||
|
# attributes
|
||||||
|
'__getattribute__',
|
||||||
|
'__getattr__',
|
||||||
|
'__setattr__',
|
||||||
|
'__delattr__',
|
||||||
|
# class instance
|
||||||
|
'__class__',
|
||||||
|
'__init__',
|
||||||
|
'__del__',
|
||||||
|
'__new__',
|
||||||
|
'__repr__',
|
||||||
|
'__qualname__',
|
||||||
|
'__objclass__',
|
||||||
|
'im_class',
|
||||||
|
'__sizeof__',
|
||||||
|
# pickle protocol
|
||||||
|
'__reduce__',
|
||||||
|
'__reduce_ex__',
|
||||||
|
'__getnewargs__',
|
||||||
|
'__getinitargs__',
|
||||||
|
'__getstate__',
|
||||||
|
'__setstate__')
|
||||||
|
)
|
||||||
|
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __new__(cls):
|
||||||
|
if cls._instance is None:
|
||||||
|
def getPlug(name):
|
||||||
|
def plug(self, *args, **kw):
|
||||||
|
raise error.PyAsn1Error('Attempted "%s" operation on ASN.1 schema object' % name)
|
||||||
|
return plug
|
||||||
|
|
||||||
|
op_names = [name
|
||||||
|
for typ in (str, int, list, dict)
|
||||||
|
for name in dir(typ)
|
||||||
|
if (name not in cls.skipMethods and
|
||||||
|
name.startswith('__') and
|
||||||
|
name.endswith('__') and
|
||||||
|
calling.callable(getattr(typ, name)))]
|
||||||
|
|
||||||
|
for name in set(op_names):
|
||||||
|
setattr(cls, name, getPlug(name))
|
||||||
|
|
||||||
|
cls._instance = object.__new__(cls)
|
||||||
|
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
if attr in self.skipMethods:
|
||||||
|
raise AttributeError('Attribute %s not present' % attr)
|
||||||
|
|
||||||
|
raise error.PyAsn1Error('Attempted "%s" operation on ASN.1 schema object' % attr)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s object>' % self.__class__.__name__
|
||||||
|
|
||||||
|
|
||||||
|
noValue = NoValue()
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleAsn1Type(Asn1Type):
|
||||||
|
"""Base class for all simple classes representing ASN.1 types.
|
||||||
|
|
||||||
|
ASN.1 distinguishes types by their ability to hold other objects.
|
||||||
|
Scalar types are known as *simple* in ASN.1.
|
||||||
|
|
||||||
|
In the user code, |ASN.1| class is normally used only for telling
|
||||||
|
ASN.1 objects from others.
|
||||||
|
|
||||||
|
Note
|
||||||
|
----
|
||||||
|
For as long as ASN.1 is concerned, a way to compare ASN.1 types
|
||||||
|
is to use :meth:`isSameTypeWith` and :meth:`isSuperTypeOf` methods.
|
||||||
|
"""
|
||||||
|
#: Default payload value
|
||||||
|
defaultValue = noValue
|
||||||
|
|
||||||
|
def __init__(self, value=noValue, **kwargs):
|
||||||
|
Asn1Type.__init__(self, **kwargs)
|
||||||
|
if value is noValue:
|
||||||
|
value = self.defaultValue
|
||||||
|
else:
|
||||||
|
value = self.prettyIn(value)
|
||||||
|
try:
|
||||||
|
self.subtypeSpec(value)
|
||||||
|
|
||||||
|
except error.PyAsn1Error:
|
||||||
|
exType, exValue, exTb = sys.exc_info()
|
||||||
|
raise exType('%s at %s' % (exValue, self.__class__.__name__))
|
||||||
|
|
||||||
|
self._value = value
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
representation = '%s %s object' % (
|
||||||
|
self.__class__.__name__, self.isValue and 'value' or 'schema')
|
||||||
|
|
||||||
|
for attr, value in self.readOnly.items():
|
||||||
|
if value:
|
||||||
|
representation += ', %s %s' % (attr, value)
|
||||||
|
|
||||||
|
if self.isValue:
|
||||||
|
value = self.prettyPrint()
|
||||||
|
if len(value) > 32:
|
||||||
|
value = value[:16] + '...' + value[-16:]
|
||||||
|
representation += ', payload [%s]' % value
|
||||||
|
|
||||||
|
return '<%s>' % representation
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self is other and True or self._value == other
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return self._value != other
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self._value < other
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self._value <= other
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self._value > other
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self._value >= other
|
||||||
|
|
||||||
|
if sys.version_info[0] <= 2:
|
||||||
|
def __nonzero__(self):
|
||||||
|
return self._value and True or False
|
||||||
|
else:
|
||||||
|
def __bool__(self):
|
||||||
|
return self._value and True or False
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self._value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def isValue(self):
|
||||||
|
"""Indicate that |ASN.1| object represents ASN.1 value.
|
||||||
|
|
||||||
|
If *isValue* is :obj:`False` then this object represents just
|
||||||
|
ASN.1 schema.
|
||||||
|
|
||||||
|
If *isValue* is :obj:`True` then, in addition to its ASN.1 schema
|
||||||
|
features, this object can also be used like a Python built-in object
|
||||||
|
(e.g. :class:`int`, :class:`str`, :class:`dict` etc.).
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :class:`bool`
|
||||||
|
:obj:`False` if object represents just ASN.1 schema.
|
||||||
|
:obj:`True` if object represents ASN.1 schema and can be used as a normal value.
|
||||||
|
|
||||||
|
Note
|
||||||
|
----
|
||||||
|
There is an important distinction between PyASN1 schema and value objects.
|
||||||
|
The PyASN1 schema objects can only participate in ASN.1 schema-related
|
||||||
|
operations (e.g. defining or testing the structure of the data). Most
|
||||||
|
obvious uses of ASN.1 schema is to guide serialisation codecs whilst
|
||||||
|
encoding/decoding serialised ASN.1 contents.
|
||||||
|
|
||||||
|
The PyASN1 value objects can **additionally** participate in many operations
|
||||||
|
involving regular Python objects (e.g. arithmetic, comprehension etc).
|
||||||
|
"""
|
||||||
|
return self._value is not noValue
|
||||||
|
|
||||||
|
def clone(self, value=noValue, **kwargs):
|
||||||
|
"""Create a modified version of |ASN.1| schema or value object.
|
||||||
|
|
||||||
|
The `clone()` method accepts the same set arguments as |ASN.1|
|
||||||
|
class takes on instantiation except that all arguments
|
||||||
|
of the `clone()` method are optional.
|
||||||
|
|
||||||
|
Whatever arguments are supplied, they are used to create a copy
|
||||||
|
of `self` taking precedence over the ones used to instantiate `self`.
|
||||||
|
|
||||||
|
Note
|
||||||
|
----
|
||||||
|
Due to the immutable nature of the |ASN.1| object, if no arguments
|
||||||
|
are supplied, no new |ASN.1| object will be created and `self` will
|
||||||
|
be returned instead.
|
||||||
|
"""
|
||||||
|
if value is noValue:
|
||||||
|
if not kwargs:
|
||||||
|
return self
|
||||||
|
|
||||||
|
value = self._value
|
||||||
|
|
||||||
|
initializers = self.readOnly.copy()
|
||||||
|
initializers.update(kwargs)
|
||||||
|
|
||||||
|
return self.__class__(value, **initializers)
|
||||||
|
|
||||||
|
def subtype(self, value=noValue, **kwargs):
|
||||||
|
"""Create a specialization of |ASN.1| schema or value object.
|
||||||
|
|
||||||
|
The subtype relationship between ASN.1 types has no correlation with
|
||||||
|
subtype relationship between Python types. ASN.1 type is mainly identified
|
||||||
|
by its tag(s) (:py:class:`~pyasn1.type.tag.TagSet`) and value range
|
||||||
|
constraints (:py:class:`~pyasn1.type.constraint.ConstraintsIntersection`).
|
||||||
|
These ASN.1 type properties are implemented as |ASN.1| attributes.
|
||||||
|
|
||||||
|
The `subtype()` method accepts the same set arguments as |ASN.1|
|
||||||
|
class takes on instantiation except that all parameters
|
||||||
|
of the `subtype()` method are optional.
|
||||||
|
|
||||||
|
With the exception of the arguments described below, the rest of
|
||||||
|
supplied arguments they are used to create a copy of `self` taking
|
||||||
|
precedence over the ones used to instantiate `self`.
|
||||||
|
|
||||||
|
The following arguments to `subtype()` create a ASN.1 subtype out of
|
||||||
|
|ASN.1| type:
|
||||||
|
|
||||||
|
Other Parameters
|
||||||
|
----------------
|
||||||
|
implicitTag: :py:class:`~pyasn1.type.tag.Tag`
|
||||||
|
Implicitly apply given ASN.1 tag object to `self`'s
|
||||||
|
:py:class:`~pyasn1.type.tag.TagSet`, then use the result as
|
||||||
|
new object's ASN.1 tag(s).
|
||||||
|
|
||||||
|
explicitTag: :py:class:`~pyasn1.type.tag.Tag`
|
||||||
|
Explicitly apply given ASN.1 tag object to `self`'s
|
||||||
|
:py:class:`~pyasn1.type.tag.TagSet`, then use the result as
|
||||||
|
new object's ASN.1 tag(s).
|
||||||
|
|
||||||
|
subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection`
|
||||||
|
Add ASN.1 constraints object to one of the `self`'s, then
|
||||||
|
use the result as new object's ASN.1 constraints.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:
|
||||||
|
new instance of |ASN.1| schema or value object
|
||||||
|
|
||||||
|
Note
|
||||||
|
----
|
||||||
|
Due to the immutable nature of the |ASN.1| object, if no arguments
|
||||||
|
are supplied, no new |ASN.1| object will be created and `self` will
|
||||||
|
be returned instead.
|
||||||
|
"""
|
||||||
|
if value is noValue:
|
||||||
|
if not kwargs:
|
||||||
|
return self
|
||||||
|
|
||||||
|
value = self._value
|
||||||
|
|
||||||
|
initializers = self.readOnly.copy()
|
||||||
|
|
||||||
|
implicitTag = kwargs.pop('implicitTag', None)
|
||||||
|
if implicitTag is not None:
|
||||||
|
initializers['tagSet'] = self.tagSet.tagImplicitly(implicitTag)
|
||||||
|
|
||||||
|
explicitTag = kwargs.pop('explicitTag', None)
|
||||||
|
if explicitTag is not None:
|
||||||
|
initializers['tagSet'] = self.tagSet.tagExplicitly(explicitTag)
|
||||||
|
|
||||||
|
for arg, option in kwargs.items():
|
||||||
|
initializers[arg] += option
|
||||||
|
|
||||||
|
return self.__class__(value, **initializers)
|
||||||
|
|
||||||
|
def prettyIn(self, value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
def prettyOut(self, value):
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
def prettyPrint(self, scope=0):
|
||||||
|
return self.prettyOut(self._value)
|
||||||
|
|
||||||
|
def prettyPrintType(self, scope=0):
|
||||||
|
return '%s -> %s' % (self.tagSet, self.__class__.__name__)
|
||||||
|
|
||||||
|
# Backward compatibility
|
||||||
|
AbstractSimpleAsn1Item = SimpleAsn1Type
|
||||||
|
|
||||||
|
#
|
||||||
|
# Constructed types:
|
||||||
|
# * There are five of them: Sequence, SequenceOf/SetOf, Set and Choice
|
||||||
|
# * ASN1 types and values are represened by Python class instances
|
||||||
|
# * Value initialization is made for defaulted components only
|
||||||
|
# * Primary method of component addressing is by-position. Data model for base
|
||||||
|
# type is Python sequence. Additional type-specific addressing methods
|
||||||
|
# may be implemented for particular types.
|
||||||
|
# * SequenceOf and SetOf types do not implement any additional methods
|
||||||
|
# * Sequence, Set and Choice types also implement by-identifier addressing
|
||||||
|
# * Sequence, Set and Choice types also implement by-asn1-type (tag) addressing
|
||||||
|
# * Sequence and Set types may include optional and defaulted
|
||||||
|
# components
|
||||||
|
# * Constructed types hold a reference to component types used for value
|
||||||
|
# verification and ordering.
|
||||||
|
# * Component type is a scalar type for SequenceOf/SetOf types and a list
|
||||||
|
# of types for Sequence/Set/Choice.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
class ConstructedAsn1Type(Asn1Type):
|
||||||
|
"""Base class for all constructed classes representing ASN.1 types.
|
||||||
|
|
||||||
|
ASN.1 distinguishes types by their ability to hold other objects.
|
||||||
|
Those "nesting" types are known as *constructed* in ASN.1.
|
||||||
|
|
||||||
|
In the user code, |ASN.1| class is normally used only for telling
|
||||||
|
ASN.1 objects from others.
|
||||||
|
|
||||||
|
Note
|
||||||
|
----
|
||||||
|
For as long as ASN.1 is concerned, a way to compare ASN.1 types
|
||||||
|
is to use :meth:`isSameTypeWith` and :meth:`isSuperTypeOf` methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: If :obj:`True`, requires exact component type matching,
|
||||||
|
#: otherwise subtype relation is only enforced
|
||||||
|
strictConstraints = False
|
||||||
|
|
||||||
|
componentType = None
|
||||||
|
|
||||||
|
# backward compatibility, unused
|
||||||
|
sizeSpec = constraint.ConstraintsIntersection()
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
readOnly = {
|
||||||
|
'componentType': self.componentType,
|
||||||
|
# backward compatibility, unused
|
||||||
|
'sizeSpec': self.sizeSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
# backward compatibility: preserve legacy sizeSpec support
|
||||||
|
kwargs = self._moveSizeSpec(**kwargs)
|
||||||
|
|
||||||
|
readOnly.update(kwargs)
|
||||||
|
|
||||||
|
Asn1Type.__init__(self, **readOnly)
|
||||||
|
|
||||||
|
def _moveSizeSpec(self, **kwargs):
|
||||||
|
# backward compatibility, unused
|
||||||
|
sizeSpec = kwargs.pop('sizeSpec', self.sizeSpec)
|
||||||
|
if sizeSpec:
|
||||||
|
subtypeSpec = kwargs.pop('subtypeSpec', self.subtypeSpec)
|
||||||
|
if subtypeSpec:
|
||||||
|
subtypeSpec = sizeSpec
|
||||||
|
|
||||||
|
else:
|
||||||
|
subtypeSpec += sizeSpec
|
||||||
|
|
||||||
|
kwargs['subtypeSpec'] = subtypeSpec
|
||||||
|
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
representation = '%s %s object' % (
|
||||||
|
self.__class__.__name__, self.isValue and 'value' or 'schema'
|
||||||
|
)
|
||||||
|
|
||||||
|
for attr, value in self.readOnly.items():
|
||||||
|
if value is not noValue:
|
||||||
|
representation += ', %s=%r' % (attr, value)
|
||||||
|
|
||||||
|
if self.isValue and self.components:
|
||||||
|
representation += ', payload [%s]' % ', '.join(
|
||||||
|
[repr(x) for x in self.components])
|
||||||
|
|
||||||
|
return '<%s>' % representation
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self is other or self.components == other
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return self.components != other
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.components < other
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self.components <= other
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.components > other
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self.components >= other
|
||||||
|
|
||||||
|
if sys.version_info[0] <= 2:
|
||||||
|
def __nonzero__(self):
|
||||||
|
return bool(self.components)
|
||||||
|
else:
|
||||||
|
def __bool__(self):
|
||||||
|
return bool(self.components)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def components(self):
|
||||||
|
raise error.PyAsn1Error('Method not implemented')
|
||||||
|
|
||||||
|
def _cloneComponentValues(self, myClone, cloneValueFlag):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def clone(self, **kwargs):
|
||||||
|
"""Create a modified version of |ASN.1| schema object.
|
||||||
|
|
||||||
|
The `clone()` method accepts the same set arguments as |ASN.1|
|
||||||
|
class takes on instantiation except that all arguments
|
||||||
|
of the `clone()` method are optional.
|
||||||
|
|
||||||
|
Whatever arguments are supplied, they are used to create a copy
|
||||||
|
of `self` taking precedence over the ones used to instantiate `self`.
|
||||||
|
|
||||||
|
Possible values of `self` are never copied over thus `clone()` can
|
||||||
|
only create a new schema object.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:
|
||||||
|
new instance of |ASN.1| type/value
|
||||||
|
|
||||||
|
Note
|
||||||
|
----
|
||||||
|
Due to the mutable nature of the |ASN.1| object, even if no arguments
|
||||||
|
are supplied, a new |ASN.1| object will be created and returned.
|
||||||
|
"""
|
||||||
|
cloneValueFlag = kwargs.pop('cloneValueFlag', False)
|
||||||
|
|
||||||
|
initializers = self.readOnly.copy()
|
||||||
|
initializers.update(kwargs)
|
||||||
|
|
||||||
|
clone = self.__class__(**initializers)
|
||||||
|
|
||||||
|
if cloneValueFlag:
|
||||||
|
self._cloneComponentValues(clone, cloneValueFlag)
|
||||||
|
|
||||||
|
return clone
|
||||||
|
|
||||||
|
def subtype(self, **kwargs):
|
||||||
|
"""Create a specialization of |ASN.1| schema object.
|
||||||
|
|
||||||
|
The `subtype()` method accepts the same set arguments as |ASN.1|
|
||||||
|
class takes on instantiation except that all parameters
|
||||||
|
of the `subtype()` method are optional.
|
||||||
|
|
||||||
|
With the exception of the arguments described below, the rest of
|
||||||
|
supplied arguments they are used to create a copy of `self` taking
|
||||||
|
precedence over the ones used to instantiate `self`.
|
||||||
|
|
||||||
|
The following arguments to `subtype()` create a ASN.1 subtype out of
|
||||||
|
|ASN.1| type.
|
||||||
|
|
||||||
|
Other Parameters
|
||||||
|
----------------
|
||||||
|
implicitTag: :py:class:`~pyasn1.type.tag.Tag`
|
||||||
|
Implicitly apply given ASN.1 tag object to `self`'s
|
||||||
|
:py:class:`~pyasn1.type.tag.TagSet`, then use the result as
|
||||||
|
new object's ASN.1 tag(s).
|
||||||
|
|
||||||
|
explicitTag: :py:class:`~pyasn1.type.tag.Tag`
|
||||||
|
Explicitly apply given ASN.1 tag object to `self`'s
|
||||||
|
:py:class:`~pyasn1.type.tag.TagSet`, then use the result as
|
||||||
|
new object's ASN.1 tag(s).
|
||||||
|
|
||||||
|
subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection`
|
||||||
|
Add ASN.1 constraints object to one of the `self`'s, then
|
||||||
|
use the result as new object's ASN.1 constraints.
|
||||||
|
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:
|
||||||
|
new instance of |ASN.1| type/value
|
||||||
|
|
||||||
|
Note
|
||||||
|
----
|
||||||
|
Due to the mutable nature of the |ASN.1| object, even if no arguments
|
||||||
|
are supplied, a new |ASN.1| object will be created and returned.
|
||||||
|
"""
|
||||||
|
|
||||||
|
initializers = self.readOnly.copy()
|
||||||
|
|
||||||
|
cloneValueFlag = kwargs.pop('cloneValueFlag', False)
|
||||||
|
|
||||||
|
implicitTag = kwargs.pop('implicitTag', None)
|
||||||
|
if implicitTag is not None:
|
||||||
|
initializers['tagSet'] = self.tagSet.tagImplicitly(implicitTag)
|
||||||
|
|
||||||
|
explicitTag = kwargs.pop('explicitTag', None)
|
||||||
|
if explicitTag is not None:
|
||||||
|
initializers['tagSet'] = self.tagSet.tagExplicitly(explicitTag)
|
||||||
|
|
||||||
|
for arg, option in kwargs.items():
|
||||||
|
initializers[arg] += option
|
||||||
|
|
||||||
|
clone = self.__class__(**initializers)
|
||||||
|
|
||||||
|
if cloneValueFlag:
|
||||||
|
self._cloneComponentValues(clone, cloneValueFlag)
|
||||||
|
|
||||||
|
return clone
|
||||||
|
|
||||||
|
def getComponentByPosition(self, idx):
|
||||||
|
raise error.PyAsn1Error('Method not implemented')
|
||||||
|
|
||||||
|
def setComponentByPosition(self, idx, value, verifyConstraints=True):
|
||||||
|
raise error.PyAsn1Error('Method not implemented')
|
||||||
|
|
||||||
|
def setComponents(self, *args, **kwargs):
|
||||||
|
for idx, value in enumerate(args):
|
||||||
|
self[idx] = value
|
||||||
|
for k in kwargs:
|
||||||
|
self[k] = kwargs[k]
|
||||||
|
return self
|
||||||
|
|
||||||
|
# backward compatibility
|
||||||
|
|
||||||
|
def setDefaultComponents(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def getComponentType(self):
|
||||||
|
return self.componentType
|
||||||
|
|
||||||
|
# backward compatibility, unused
|
||||||
|
def verifySizeSpec(self):
|
||||||
|
self.subtypeSpec(self)
|
||||||
|
|
||||||
|
|
||||||
|
# Backward compatibility
|
||||||
|
AbstractConstructedAsn1Item = ConstructedAsn1Type
|
||||||
335
pyasn1/type/char.py
Normal file
335
pyasn1/type/char.py
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from pyasn1 import error
|
||||||
|
from pyasn1.type import tag
|
||||||
|
from pyasn1.type import univ
|
||||||
|
|
||||||
|
__all__ = ['NumericString', 'PrintableString', 'TeletexString', 'T61String', 'VideotexString',
|
||||||
|
'IA5String', 'GraphicString', 'VisibleString', 'ISO646String',
|
||||||
|
'GeneralString', 'UniversalString', 'BMPString', 'UTF8String']
|
||||||
|
|
||||||
|
NoValue = univ.NoValue
|
||||||
|
noValue = univ.noValue
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractCharacterString(univ.OctetString):
|
||||||
|
"""Creates |ASN.1| schema or value object.
|
||||||
|
|
||||||
|
|ASN.1| class is based on :class:`~pyasn1.type.base.SimpleAsn1Type`,
|
||||||
|
its objects are immutable and duck-type Python 2 :class:`str` or Python 3
|
||||||
|
:class:`bytes`. When used in octet-stream context, |ASN.1| type assumes
|
||||||
|
"|encoding|" encoding.
|
||||||
|
|
||||||
|
Keyword Args
|
||||||
|
------------
|
||||||
|
value: :class:`unicode`, :class:`str`, :class:`bytes` or |ASN.1| object
|
||||||
|
:class:`unicode` object (Python 2) or :class:`str` (Python 3),
|
||||||
|
alternatively :class:`str` (Python 2) or :class:`bytes` (Python 3)
|
||||||
|
representing octet-stream of serialised unicode string
|
||||||
|
(note `encoding` parameter) or |ASN.1| class instance.
|
||||||
|
If `value` is not given, schema object will be created.
|
||||||
|
|
||||||
|
tagSet: :py:class:`~pyasn1.type.tag.TagSet`
|
||||||
|
Object representing non-default ASN.1 tag(s)
|
||||||
|
|
||||||
|
subtypeSpec: :py:class:`~pyasn1.type.constraint.ConstraintsIntersection`
|
||||||
|
Object representing non-default ASN.1 subtype constraint(s). Constraints
|
||||||
|
verification for |ASN.1| type occurs automatically on object
|
||||||
|
instantiation.
|
||||||
|
|
||||||
|
encoding: :py:class:`str`
|
||||||
|
Unicode codec ID to encode/decode :class:`unicode` (Python 2) or
|
||||||
|
:class:`str` (Python 3) the payload when |ASN.1| object is used
|
||||||
|
in octet-stream context.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
~pyasn1.error.ValueConstraintError, ~pyasn1.error.PyAsn1Error
|
||||||
|
On constraint violation or bad initializer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if sys.version_info[0] <= 2:
|
||||||
|
def __str__(self):
|
||||||
|
try:
|
||||||
|
# `str` is Py2 text representation
|
||||||
|
return self._value.encode(self.encoding)
|
||||||
|
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
exc = sys.exc_info()[1]
|
||||||
|
raise error.PyAsn1UnicodeEncodeError(
|
||||||
|
"Can't encode string '%s' with codec "
|
||||||
|
"%s" % (self._value, self.encoding), exc
|
||||||
|
)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return unicode(self._value)
|
||||||
|
|
||||||
|
def prettyIn(self, value):
|
||||||
|
try:
|
||||||
|
if isinstance(value, unicode):
|
||||||
|
return value
|
||||||
|
elif isinstance(value, str):
|
||||||
|
return value.decode(self.encoding)
|
||||||
|
elif isinstance(value, (tuple, list)):
|
||||||
|
return self.prettyIn(''.join([chr(x) for x in value]))
|
||||||
|
elif isinstance(value, univ.OctetString):
|
||||||
|
return value.asOctets().decode(self.encoding)
|
||||||
|
else:
|
||||||
|
return unicode(value)
|
||||||
|
|
||||||
|
except (UnicodeDecodeError, LookupError):
|
||||||
|
exc = sys.exc_info()[1]
|
||||||
|
raise error.PyAsn1UnicodeDecodeError(
|
||||||
|
"Can't decode string '%s' with codec "
|
||||||
|
"%s" % (value, self.encoding), exc
|
||||||
|
)
|
||||||
|
|
||||||
|
def asOctets(self, padding=True):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
def asNumbers(self, padding=True):
|
||||||
|
return tuple([ord(x) for x in str(self)])
|
||||||
|
|
||||||
|
else:
|
||||||
|
def __str__(self):
|
||||||
|
# `unicode` is Py3 text representation
|
||||||
|
return str(self._value)
|
||||||
|
|
||||||
|
def __bytes__(self):
|
||||||
|
try:
|
||||||
|
return self._value.encode(self.encoding)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
exc = sys.exc_info()[1]
|
||||||
|
raise error.PyAsn1UnicodeEncodeError(
|
||||||
|
"Can't encode string '%s' with codec "
|
||||||
|
"%s" % (self._value, self.encoding), exc
|
||||||
|
)
|
||||||
|
|
||||||
|
def prettyIn(self, value):
|
||||||
|
try:
|
||||||
|
if isinstance(value, str):
|
||||||
|
return value
|
||||||
|
elif isinstance(value, bytes):
|
||||||
|
return value.decode(self.encoding)
|
||||||
|
elif isinstance(value, (tuple, list)):
|
||||||
|
return self.prettyIn(bytes(value))
|
||||||
|
elif isinstance(value, univ.OctetString):
|
||||||
|
return value.asOctets().decode(self.encoding)
|
||||||
|
else:
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
except (UnicodeDecodeError, LookupError):
|
||||||
|
exc = sys.exc_info()[1]
|
||||||
|
raise error.PyAsn1UnicodeDecodeError(
|
||||||
|
"Can't decode string '%s' with codec "
|
||||||
|
"%s" % (value, self.encoding), exc
|
||||||
|
)
|
||||||
|
|
||||||
|
def asOctets(self, padding=True):
|
||||||
|
return bytes(self)
|
||||||
|
|
||||||
|
def asNumbers(self, padding=True):
|
||||||
|
return tuple(bytes(self))
|
||||||
|
|
||||||
|
#
|
||||||
|
# See OctetString.prettyPrint() for the explanation
|
||||||
|
#
|
||||||
|
|
||||||
|
def prettyOut(self, value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
def prettyPrint(self, scope=0):
|
||||||
|
# first see if subclass has its own .prettyOut()
|
||||||
|
value = self.prettyOut(self._value)
|
||||||
|
|
||||||
|
if value is not self._value:
|
||||||
|
return value
|
||||||
|
|
||||||
|
return AbstractCharacterString.__str__(self)
|
||||||
|
|
||||||
|
def __reversed__(self):
|
||||||
|
return reversed(self._value)
|
||||||
|
|
||||||
|
|
||||||
|
class NumericString(AbstractCharacterString):
|
||||||
|
__doc__ = AbstractCharacterString.__doc__
|
||||||
|
|
||||||
|
#: Set (on class, not on instance) or return a
|
||||||
|
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||||
|
#: associated with |ASN.1| type.
|
||||||
|
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||||
|
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 18)
|
||||||
|
)
|
||||||
|
encoding = 'us-ascii'
|
||||||
|
|
||||||
|
# Optimization for faster codec lookup
|
||||||
|
typeId = AbstractCharacterString.getTypeId()
|
||||||
|
|
||||||
|
|
||||||
|
class PrintableString(AbstractCharacterString):
|
||||||
|
__doc__ = AbstractCharacterString.__doc__
|
||||||
|
|
||||||
|
#: Set (on class, not on instance) or return a
|
||||||
|
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||||
|
#: associated with |ASN.1| type.
|
||||||
|
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||||
|
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 19)
|
||||||
|
)
|
||||||
|
encoding = 'us-ascii'
|
||||||
|
|
||||||
|
# Optimization for faster codec lookup
|
||||||
|
typeId = AbstractCharacterString.getTypeId()
|
||||||
|
|
||||||
|
|
||||||
|
class TeletexString(AbstractCharacterString):
|
||||||
|
__doc__ = AbstractCharacterString.__doc__
|
||||||
|
|
||||||
|
#: Set (on class, not on instance) or return a
|
||||||
|
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||||
|
#: associated with |ASN.1| type.
|
||||||
|
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||||
|
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 20)
|
||||||
|
)
|
||||||
|
encoding = 'iso-8859-1'
|
||||||
|
|
||||||
|
# Optimization for faster codec lookup
|
||||||
|
typeId = AbstractCharacterString.getTypeId()
|
||||||
|
|
||||||
|
|
||||||
|
class T61String(TeletexString):
|
||||||
|
__doc__ = TeletexString.__doc__
|
||||||
|
|
||||||
|
# Optimization for faster codec lookup
|
||||||
|
typeId = AbstractCharacterString.getTypeId()
|
||||||
|
|
||||||
|
|
||||||
|
class VideotexString(AbstractCharacterString):
|
||||||
|
__doc__ = AbstractCharacterString.__doc__
|
||||||
|
|
||||||
|
#: Set (on class, not on instance) or return a
|
||||||
|
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||||
|
#: associated with |ASN.1| type.
|
||||||
|
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||||
|
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 21)
|
||||||
|
)
|
||||||
|
encoding = 'iso-8859-1'
|
||||||
|
|
||||||
|
# Optimization for faster codec lookup
|
||||||
|
typeId = AbstractCharacterString.getTypeId()
|
||||||
|
|
||||||
|
|
||||||
|
class IA5String(AbstractCharacterString):
|
||||||
|
__doc__ = AbstractCharacterString.__doc__
|
||||||
|
|
||||||
|
#: Set (on class, not on instance) or return a
|
||||||
|
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||||
|
#: associated with |ASN.1| type.
|
||||||
|
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||||
|
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 22)
|
||||||
|
)
|
||||||
|
encoding = 'us-ascii'
|
||||||
|
|
||||||
|
# Optimization for faster codec lookup
|
||||||
|
typeId = AbstractCharacterString.getTypeId()
|
||||||
|
|
||||||
|
|
||||||
|
class GraphicString(AbstractCharacterString):
|
||||||
|
__doc__ = AbstractCharacterString.__doc__
|
||||||
|
|
||||||
|
#: Set (on class, not on instance) or return a
|
||||||
|
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||||
|
#: associated with |ASN.1| type.
|
||||||
|
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||||
|
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 25)
|
||||||
|
)
|
||||||
|
encoding = 'iso-8859-1'
|
||||||
|
|
||||||
|
# Optimization for faster codec lookup
|
||||||
|
typeId = AbstractCharacterString.getTypeId()
|
||||||
|
|
||||||
|
|
||||||
|
class VisibleString(AbstractCharacterString):
|
||||||
|
__doc__ = AbstractCharacterString.__doc__
|
||||||
|
|
||||||
|
#: Set (on class, not on instance) or return a
|
||||||
|
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||||
|
#: associated with |ASN.1| type.
|
||||||
|
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||||
|
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 26)
|
||||||
|
)
|
||||||
|
encoding = 'us-ascii'
|
||||||
|
|
||||||
|
# Optimization for faster codec lookup
|
||||||
|
typeId = AbstractCharacterString.getTypeId()
|
||||||
|
|
||||||
|
|
||||||
|
class ISO646String(VisibleString):
|
||||||
|
__doc__ = VisibleString.__doc__
|
||||||
|
|
||||||
|
# Optimization for faster codec lookup
|
||||||
|
typeId = AbstractCharacterString.getTypeId()
|
||||||
|
|
||||||
|
class GeneralString(AbstractCharacterString):
|
||||||
|
__doc__ = AbstractCharacterString.__doc__
|
||||||
|
|
||||||
|
#: Set (on class, not on instance) or return a
|
||||||
|
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||||
|
#: associated with |ASN.1| type.
|
||||||
|
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||||
|
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 27)
|
||||||
|
)
|
||||||
|
encoding = 'iso-8859-1'
|
||||||
|
|
||||||
|
# Optimization for faster codec lookup
|
||||||
|
typeId = AbstractCharacterString.getTypeId()
|
||||||
|
|
||||||
|
|
||||||
|
class UniversalString(AbstractCharacterString):
|
||||||
|
__doc__ = AbstractCharacterString.__doc__
|
||||||
|
|
||||||
|
#: Set (on class, not on instance) or return a
|
||||||
|
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||||
|
#: associated with |ASN.1| type.
|
||||||
|
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||||
|
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 28)
|
||||||
|
)
|
||||||
|
encoding = "utf-32-be"
|
||||||
|
|
||||||
|
# Optimization for faster codec lookup
|
||||||
|
typeId = AbstractCharacterString.getTypeId()
|
||||||
|
|
||||||
|
|
||||||
|
class BMPString(AbstractCharacterString):
|
||||||
|
__doc__ = AbstractCharacterString.__doc__
|
||||||
|
|
||||||
|
#: Set (on class, not on instance) or return a
|
||||||
|
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||||
|
#: associated with |ASN.1| type.
|
||||||
|
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||||
|
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 30)
|
||||||
|
)
|
||||||
|
encoding = "utf-16-be"
|
||||||
|
|
||||||
|
# Optimization for faster codec lookup
|
||||||
|
typeId = AbstractCharacterString.getTypeId()
|
||||||
|
|
||||||
|
|
||||||
|
class UTF8String(AbstractCharacterString):
|
||||||
|
__doc__ = AbstractCharacterString.__doc__
|
||||||
|
|
||||||
|
#: Set (on class, not on instance) or return a
|
||||||
|
#: :py:class:`~pyasn1.type.tag.TagSet` object representing ASN.1 tag(s)
|
||||||
|
#: associated with |ASN.1| type.
|
||||||
|
tagSet = AbstractCharacterString.tagSet.tagImplicitly(
|
||||||
|
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 12)
|
||||||
|
)
|
||||||
|
encoding = "utf-8"
|
||||||
|
|
||||||
|
# Optimization for faster codec lookup
|
||||||
|
typeId = AbstractCharacterString.getTypeId()
|
||||||
756
pyasn1/type/constraint.py
Normal file
756
pyasn1/type/constraint.py
Normal file
@ -0,0 +1,756 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
# Original concept and code by Mike C. Fletcher.
|
||||||
|
#
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from pyasn1.type import error
|
||||||
|
|
||||||
|
__all__ = ['SingleValueConstraint', 'ContainedSubtypeConstraint',
|
||||||
|
'ValueRangeConstraint', 'ValueSizeConstraint',
|
||||||
|
'PermittedAlphabetConstraint', 'InnerTypeConstraint',
|
||||||
|
'ConstraintsExclusion', 'ConstraintsIntersection',
|
||||||
|
'ConstraintsUnion']
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractConstraint(object):
|
||||||
|
|
||||||
|
def __init__(self, *values):
|
||||||
|
self._valueMap = set()
|
||||||
|
self._setValues(values)
|
||||||
|
self.__hash = hash((self.__class__.__name__, self._values))
|
||||||
|
|
||||||
|
def __call__(self, value, idx=None):
|
||||||
|
if not self._values:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._testValue(value, idx)
|
||||||
|
|
||||||
|
except error.ValueConstraintError:
|
||||||
|
raise error.ValueConstraintError(
|
||||||
|
'%s failed at: %r' % (self, sys.exc_info()[1])
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
representation = '%s object' % (self.__class__.__name__)
|
||||||
|
|
||||||
|
if self._values:
|
||||||
|
representation += ', consts %s' % ', '.join(
|
||||||
|
[repr(x) for x in self._values])
|
||||||
|
|
||||||
|
return '<%s>' % representation
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self is other and True or self._values == other
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return self._values != other
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self._values < other
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self._values <= other
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self._values > other
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self._values >= other
|
||||||
|
|
||||||
|
if sys.version_info[0] <= 2:
|
||||||
|
def __nonzero__(self):
|
||||||
|
return self._values and True or False
|
||||||
|
else:
|
||||||
|
def __bool__(self):
|
||||||
|
return self._values and True or False
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return self.__hash
|
||||||
|
|
||||||
|
def _setValues(self, values):
|
||||||
|
self._values = values
|
||||||
|
|
||||||
|
def _testValue(self, value, idx):
|
||||||
|
raise error.ValueConstraintError(value)
|
||||||
|
|
||||||
|
# Constraints derivation logic
|
||||||
|
def getValueMap(self):
|
||||||
|
return self._valueMap
|
||||||
|
|
||||||
|
def isSuperTypeOf(self, otherConstraint):
|
||||||
|
# TODO: fix possible comparison of set vs scalars here
|
||||||
|
return (otherConstraint is self or
|
||||||
|
not self._values or
|
||||||
|
otherConstraint == self or
|
||||||
|
self in otherConstraint.getValueMap())
|
||||||
|
|
||||||
|
def isSubTypeOf(self, otherConstraint):
|
||||||
|
return (otherConstraint is self or
|
||||||
|
not self or
|
||||||
|
otherConstraint == self or
|
||||||
|
otherConstraint in self._valueMap)
|
||||||
|
|
||||||
|
|
||||||
|
class SingleValueConstraint(AbstractConstraint):
|
||||||
|
"""Create a SingleValueConstraint object.
|
||||||
|
|
||||||
|
The SingleValueConstraint satisfies any value that
|
||||||
|
is present in the set of permitted values.
|
||||||
|
|
||||||
|
Objects of this type are iterable (emitting constraint values) and
|
||||||
|
can act as operands for some arithmetic operations e.g. addition
|
||||||
|
and subtraction. The latter can be used for combining multiple
|
||||||
|
SingleValueConstraint objects into one.
|
||||||
|
|
||||||
|
The SingleValueConstraint object can be applied to
|
||||||
|
any ASN.1 type.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
*values: :class:`int`
|
||||||
|
Full set of values permitted by this constraint object.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class DivisorOfSix(Integer):
|
||||||
|
'''
|
||||||
|
ASN.1 specification:
|
||||||
|
|
||||||
|
Divisor-Of-6 ::= INTEGER (1 | 2 | 3 | 6)
|
||||||
|
'''
|
||||||
|
subtypeSpec = SingleValueConstraint(1, 2, 3, 6)
|
||||||
|
|
||||||
|
# this will succeed
|
||||||
|
divisor_of_six = DivisorOfSix(1)
|
||||||
|
|
||||||
|
# this will raise ValueConstraintError
|
||||||
|
divisor_of_six = DivisorOfSix(7)
|
||||||
|
"""
|
||||||
|
def _setValues(self, values):
|
||||||
|
self._values = values
|
||||||
|
self._set = set(values)
|
||||||
|
|
||||||
|
def _testValue(self, value, idx):
|
||||||
|
if value not in self._set:
|
||||||
|
raise error.ValueConstraintError(value)
|
||||||
|
|
||||||
|
# Constrains can be merged or reduced
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
return item in self._set
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._set)
|
||||||
|
|
||||||
|
def __sub__(self, constraint):
|
||||||
|
return self.__class__(*(self._set.difference(constraint)))
|
||||||
|
|
||||||
|
def __add__(self, constraint):
|
||||||
|
return self.__class__(*(self._set.union(constraint)))
|
||||||
|
|
||||||
|
def __sub__(self, constraint):
|
||||||
|
return self.__class__(*(self._set.difference(constraint)))
|
||||||
|
|
||||||
|
|
||||||
|
class ContainedSubtypeConstraint(AbstractConstraint):
|
||||||
|
"""Create a ContainedSubtypeConstraint object.
|
||||||
|
|
||||||
|
The ContainedSubtypeConstraint satisfies any value that
|
||||||
|
is present in the set of permitted values and also
|
||||||
|
satisfies included constraints.
|
||||||
|
|
||||||
|
The ContainedSubtypeConstraint object can be applied to
|
||||||
|
any ASN.1 type.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
*values:
|
||||||
|
Full set of values and constraint objects permitted
|
||||||
|
by this constraint object.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class DivisorOfEighteen(Integer):
|
||||||
|
'''
|
||||||
|
ASN.1 specification:
|
||||||
|
|
||||||
|
Divisors-of-18 ::= INTEGER (INCLUDES Divisors-of-6 | 9 | 18)
|
||||||
|
'''
|
||||||
|
subtypeSpec = ContainedSubtypeConstraint(
|
||||||
|
SingleValueConstraint(1, 2, 3, 6), 9, 18
|
||||||
|
)
|
||||||
|
|
||||||
|
# this will succeed
|
||||||
|
divisor_of_eighteen = DivisorOfEighteen(9)
|
||||||
|
|
||||||
|
# this will raise ValueConstraintError
|
||||||
|
divisor_of_eighteen = DivisorOfEighteen(10)
|
||||||
|
"""
|
||||||
|
def _testValue(self, value, idx):
|
||||||
|
for constraint in self._values:
|
||||||
|
if isinstance(constraint, AbstractConstraint):
|
||||||
|
constraint(value, idx)
|
||||||
|
elif value not in self._set:
|
||||||
|
raise error.ValueConstraintError(value)
|
||||||
|
|
||||||
|
|
||||||
|
class ValueRangeConstraint(AbstractConstraint):
|
||||||
|
"""Create a ValueRangeConstraint object.
|
||||||
|
|
||||||
|
The ValueRangeConstraint satisfies any value that
|
||||||
|
falls in the range of permitted values.
|
||||||
|
|
||||||
|
The ValueRangeConstraint object can only be applied
|
||||||
|
to :class:`~pyasn1.type.univ.Integer` and
|
||||||
|
:class:`~pyasn1.type.univ.Real` types.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
start: :class:`int`
|
||||||
|
Minimum permitted value in the range (inclusive)
|
||||||
|
|
||||||
|
end: :class:`int`
|
||||||
|
Maximum permitted value in the range (inclusive)
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class TeenAgeYears(Integer):
|
||||||
|
'''
|
||||||
|
ASN.1 specification:
|
||||||
|
|
||||||
|
TeenAgeYears ::= INTEGER (13 .. 19)
|
||||||
|
'''
|
||||||
|
subtypeSpec = ValueRangeConstraint(13, 19)
|
||||||
|
|
||||||
|
# this will succeed
|
||||||
|
teen_year = TeenAgeYears(18)
|
||||||
|
|
||||||
|
# this will raise ValueConstraintError
|
||||||
|
teen_year = TeenAgeYears(20)
|
||||||
|
"""
|
||||||
|
def _testValue(self, value, idx):
|
||||||
|
if value < self.start or value > self.stop:
|
||||||
|
raise error.ValueConstraintError(value)
|
||||||
|
|
||||||
|
def _setValues(self, values):
|
||||||
|
if len(values) != 2:
|
||||||
|
raise error.PyAsn1Error(
|
||||||
|
'%s: bad constraint values' % (self.__class__.__name__,)
|
||||||
|
)
|
||||||
|
self.start, self.stop = values
|
||||||
|
if self.start > self.stop:
|
||||||
|
raise error.PyAsn1Error(
|
||||||
|
'%s: screwed constraint values (start > stop): %s > %s' % (
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.start, self.stop
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AbstractConstraint._setValues(self, values)
|
||||||
|
|
||||||
|
|
||||||
|
class ValueSizeConstraint(ValueRangeConstraint):
|
||||||
|
"""Create a ValueSizeConstraint object.
|
||||||
|
|
||||||
|
The ValueSizeConstraint satisfies any value for
|
||||||
|
as long as its size falls within the range of
|
||||||
|
permitted sizes.
|
||||||
|
|
||||||
|
The ValueSizeConstraint object can be applied
|
||||||
|
to :class:`~pyasn1.type.univ.BitString`,
|
||||||
|
:class:`~pyasn1.type.univ.OctetString` (including
|
||||||
|
all :ref:`character ASN.1 types <type.char>`),
|
||||||
|
:class:`~pyasn1.type.univ.SequenceOf`
|
||||||
|
and :class:`~pyasn1.type.univ.SetOf` types.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
minimum: :class:`int`
|
||||||
|
Minimum permitted size of the value (inclusive)
|
||||||
|
|
||||||
|
maximum: :class:`int`
|
||||||
|
Maximum permitted size of the value (inclusive)
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class BaseballTeamRoster(SetOf):
|
||||||
|
'''
|
||||||
|
ASN.1 specification:
|
||||||
|
|
||||||
|
BaseballTeamRoster ::= SET SIZE (1..25) OF PlayerNames
|
||||||
|
'''
|
||||||
|
componentType = PlayerNames()
|
||||||
|
subtypeSpec = ValueSizeConstraint(1, 25)
|
||||||
|
|
||||||
|
# this will succeed
|
||||||
|
team = BaseballTeamRoster()
|
||||||
|
team.extend(['Jan', 'Matej'])
|
||||||
|
encode(team)
|
||||||
|
|
||||||
|
# this will raise ValueConstraintError
|
||||||
|
team = BaseballTeamRoster()
|
||||||
|
team.extend(['Jan'] * 26)
|
||||||
|
encode(team)
|
||||||
|
|
||||||
|
Note
|
||||||
|
----
|
||||||
|
Whenever ValueSizeConstraint is applied to mutable types
|
||||||
|
(e.g. :class:`~pyasn1.type.univ.SequenceOf`,
|
||||||
|
:class:`~pyasn1.type.univ.SetOf`), constraint
|
||||||
|
validation only happens at the serialisation phase rather
|
||||||
|
than schema instantiation phase (as it is with immutable
|
||||||
|
types).
|
||||||
|
"""
|
||||||
|
def _testValue(self, value, idx):
|
||||||
|
valueSize = len(value)
|
||||||
|
if valueSize < self.start or valueSize > self.stop:
|
||||||
|
raise error.ValueConstraintError(value)
|
||||||
|
|
||||||
|
|
||||||
|
class PermittedAlphabetConstraint(SingleValueConstraint):
|
||||||
|
"""Create a PermittedAlphabetConstraint object.
|
||||||
|
|
||||||
|
The PermittedAlphabetConstraint satisfies any character
|
||||||
|
string for as long as all its characters are present in
|
||||||
|
the set of permitted characters.
|
||||||
|
|
||||||
|
Objects of this type are iterable (emitting constraint values) and
|
||||||
|
can act as operands for some arithmetic operations e.g. addition
|
||||||
|
and subtraction.
|
||||||
|
|
||||||
|
The PermittedAlphabetConstraint object can only be applied
|
||||||
|
to the :ref:`character ASN.1 types <type.char>` such as
|
||||||
|
:class:`~pyasn1.type.char.IA5String`.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
*alphabet: :class:`str`
|
||||||
|
Full set of characters permitted by this constraint object.
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class BooleanValue(IA5String):
|
||||||
|
'''
|
||||||
|
ASN.1 specification:
|
||||||
|
|
||||||
|
BooleanValue ::= IA5String (FROM ('T' | 'F'))
|
||||||
|
'''
|
||||||
|
subtypeSpec = PermittedAlphabetConstraint('T', 'F')
|
||||||
|
|
||||||
|
# this will succeed
|
||||||
|
truth = BooleanValue('T')
|
||||||
|
truth = BooleanValue('TF')
|
||||||
|
|
||||||
|
# this will raise ValueConstraintError
|
||||||
|
garbage = BooleanValue('TAF')
|
||||||
|
|
||||||
|
ASN.1 `FROM ... EXCEPT ...` clause can be modelled by combining multiple
|
||||||
|
PermittedAlphabetConstraint objects into one:
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class Lipogramme(IA5String):
|
||||||
|
'''
|
||||||
|
ASN.1 specification:
|
||||||
|
|
||||||
|
Lipogramme ::=
|
||||||
|
IA5String (FROM (ALL EXCEPT ("e"|"E")))
|
||||||
|
'''
|
||||||
|
subtypeSpec = (
|
||||||
|
PermittedAlphabetConstraint(*string.printable) -
|
||||||
|
PermittedAlphabetConstraint('e', 'E')
|
||||||
|
)
|
||||||
|
|
||||||
|
# this will succeed
|
||||||
|
lipogramme = Lipogramme('A work of fiction?')
|
||||||
|
|
||||||
|
# this will raise ValueConstraintError
|
||||||
|
lipogramme = Lipogramme('Eel')
|
||||||
|
|
||||||
|
Note
|
||||||
|
----
|
||||||
|
Although `ConstraintsExclusion` object could seemingly be used for this
|
||||||
|
purpose, practically, for it to work, it needs to represent its operand
|
||||||
|
constraints as sets and intersect one with the other. That would require
|
||||||
|
the insight into the constraint values (and their types) that are otherwise
|
||||||
|
hidden inside the constraint object.
|
||||||
|
|
||||||
|
Therefore it's more practical to model `EXCEPT` clause at
|
||||||
|
`PermittedAlphabetConstraint` level instead.
|
||||||
|
"""
|
||||||
|
def _setValues(self, values):
|
||||||
|
self._values = values
|
||||||
|
self._set = set(values)
|
||||||
|
|
||||||
|
def _testValue(self, value, idx):
|
||||||
|
if not self._set.issuperset(value):
|
||||||
|
raise error.ValueConstraintError(value)
|
||||||
|
|
||||||
|
|
||||||
|
class ComponentPresentConstraint(AbstractConstraint):
|
||||||
|
"""Create a ComponentPresentConstraint object.
|
||||||
|
|
||||||
|
The ComponentPresentConstraint is only satisfied when the value
|
||||||
|
is not `None`.
|
||||||
|
|
||||||
|
The ComponentPresentConstraint object is typically used with
|
||||||
|
`WithComponentsConstraint`.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
present = ComponentPresentConstraint()
|
||||||
|
|
||||||
|
# this will succeed
|
||||||
|
present('whatever')
|
||||||
|
|
||||||
|
# this will raise ValueConstraintError
|
||||||
|
present(None)
|
||||||
|
"""
|
||||||
|
def _setValues(self, values):
|
||||||
|
self._values = ('<must be present>',)
|
||||||
|
|
||||||
|
if values:
|
||||||
|
raise error.PyAsn1Error('No arguments expected')
|
||||||
|
|
||||||
|
def _testValue(self, value, idx):
|
||||||
|
if value is None:
|
||||||
|
raise error.ValueConstraintError(
|
||||||
|
'Component is not present:')
|
||||||
|
|
||||||
|
|
||||||
|
class ComponentAbsentConstraint(AbstractConstraint):
|
||||||
|
"""Create a ComponentAbsentConstraint object.
|
||||||
|
|
||||||
|
The ComponentAbsentConstraint is only satisfied when the value
|
||||||
|
is `None`.
|
||||||
|
|
||||||
|
The ComponentAbsentConstraint object is typically used with
|
||||||
|
`WithComponentsConstraint`.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
absent = ComponentAbsentConstraint()
|
||||||
|
|
||||||
|
# this will succeed
|
||||||
|
absent(None)
|
||||||
|
|
||||||
|
# this will raise ValueConstraintError
|
||||||
|
absent('whatever')
|
||||||
|
"""
|
||||||
|
def _setValues(self, values):
|
||||||
|
self._values = ('<must be absent>',)
|
||||||
|
|
||||||
|
if values:
|
||||||
|
raise error.PyAsn1Error('No arguments expected')
|
||||||
|
|
||||||
|
def _testValue(self, value, idx):
|
||||||
|
if value is not None:
|
||||||
|
raise error.ValueConstraintError(
|
||||||
|
'Component is not absent: %r' % value)
|
||||||
|
|
||||||
|
|
||||||
|
class WithComponentsConstraint(AbstractConstraint):
|
||||||
|
"""Create a WithComponentsConstraint object.
|
||||||
|
|
||||||
|
The `WithComponentsConstraint` satisfies any mapping object that has
|
||||||
|
constrained fields present or absent, what is indicated by
|
||||||
|
`ComponentPresentConstraint` and `ComponentAbsentConstraint`
|
||||||
|
objects respectively.
|
||||||
|
|
||||||
|
The `WithComponentsConstraint` object is typically applied
|
||||||
|
to :class:`~pyasn1.type.univ.Set` or
|
||||||
|
:class:`~pyasn1.type.univ.Sequence` types.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
*fields: :class:`tuple`
|
||||||
|
Zero or more tuples of (`field`, `constraint`) indicating constrained
|
||||||
|
fields.
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
On top of the primary use of `WithComponentsConstraint` (ensuring presence
|
||||||
|
or absence of particular components of a :class:`~pyasn1.type.univ.Set` or
|
||||||
|
:class:`~pyasn1.type.univ.Sequence`), it is also possible to pass any other
|
||||||
|
constraint objects or their combinations. In case of scalar fields, these
|
||||||
|
constraints will be verified in addition to the constraints belonging to
|
||||||
|
scalar components themselves. However, formally, these additional
|
||||||
|
constraints do not change the type of these ASN.1 objects.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class Item(Sequence): # Set is similar
|
||||||
|
'''
|
||||||
|
ASN.1 specification:
|
||||||
|
|
||||||
|
Item ::= SEQUENCE {
|
||||||
|
id INTEGER OPTIONAL,
|
||||||
|
name OCTET STRING OPTIONAL
|
||||||
|
} WITH COMPONENTS id PRESENT, name ABSENT | id ABSENT, name PRESENT
|
||||||
|
'''
|
||||||
|
componentType = NamedTypes(
|
||||||
|
OptionalNamedType('id', Integer()),
|
||||||
|
OptionalNamedType('name', OctetString())
|
||||||
|
)
|
||||||
|
withComponents = ConstraintsUnion(
|
||||||
|
WithComponentsConstraint(
|
||||||
|
('id', ComponentPresentConstraint()),
|
||||||
|
('name', ComponentAbsentConstraint())
|
||||||
|
),
|
||||||
|
WithComponentsConstraint(
|
||||||
|
('id', ComponentAbsentConstraint()),
|
||||||
|
('name', ComponentPresentConstraint())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
item = Item()
|
||||||
|
|
||||||
|
# This will succeed
|
||||||
|
item['id'] = 1
|
||||||
|
|
||||||
|
# This will succeed
|
||||||
|
item.reset()
|
||||||
|
item['name'] = 'John'
|
||||||
|
|
||||||
|
# This will fail (on encoding)
|
||||||
|
item.reset()
|
||||||
|
descr['id'] = 1
|
||||||
|
descr['name'] = 'John'
|
||||||
|
"""
|
||||||
|
def _testValue(self, value, idx):
|
||||||
|
for field, constraint in self._values:
|
||||||
|
constraint(value.get(field))
|
||||||
|
|
||||||
|
def _setValues(self, values):
|
||||||
|
AbstractConstraint._setValues(self, values)
|
||||||
|
|
||||||
|
|
||||||
|
# This is a bit kludgy, meaning two op modes within a single constraint
|
||||||
|
class InnerTypeConstraint(AbstractConstraint):
|
||||||
|
"""Value must satisfy the type and presence constraints"""
|
||||||
|
|
||||||
|
def _testValue(self, value, idx):
|
||||||
|
if self.__singleTypeConstraint:
|
||||||
|
self.__singleTypeConstraint(value)
|
||||||
|
elif self.__multipleTypeConstraint:
|
||||||
|
if idx not in self.__multipleTypeConstraint:
|
||||||
|
raise error.ValueConstraintError(value)
|
||||||
|
constraint, status = self.__multipleTypeConstraint[idx]
|
||||||
|
if status == 'ABSENT': # XXX presence is not checked!
|
||||||
|
raise error.ValueConstraintError(value)
|
||||||
|
constraint(value)
|
||||||
|
|
||||||
|
def _setValues(self, values):
|
||||||
|
self.__multipleTypeConstraint = {}
|
||||||
|
self.__singleTypeConstraint = None
|
||||||
|
for v in values:
|
||||||
|
if isinstance(v, tuple):
|
||||||
|
self.__multipleTypeConstraint[v[0]] = v[1], v[2]
|
||||||
|
else:
|
||||||
|
self.__singleTypeConstraint = v
|
||||||
|
AbstractConstraint._setValues(self, values)
|
||||||
|
|
||||||
|
|
||||||
|
# Logic operations on constraints
|
||||||
|
|
||||||
|
class ConstraintsExclusion(AbstractConstraint):
|
||||||
|
"""Create a ConstraintsExclusion logic operator object.
|
||||||
|
|
||||||
|
The ConstraintsExclusion logic operator succeeds when the
|
||||||
|
value does *not* satisfy the operand constraint.
|
||||||
|
|
||||||
|
The ConstraintsExclusion object can be applied to
|
||||||
|
any constraint and logic operator object.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
*constraints:
|
||||||
|
Constraint or logic operator objects.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class LuckyNumber(Integer):
|
||||||
|
subtypeSpec = ConstraintsExclusion(
|
||||||
|
SingleValueConstraint(13)
|
||||||
|
)
|
||||||
|
|
||||||
|
# this will succeed
|
||||||
|
luckyNumber = LuckyNumber(12)
|
||||||
|
|
||||||
|
# this will raise ValueConstraintError
|
||||||
|
luckyNumber = LuckyNumber(13)
|
||||||
|
|
||||||
|
Note
|
||||||
|
----
|
||||||
|
The `FROM ... EXCEPT ...` ASN.1 clause should be modeled by combining
|
||||||
|
constraint objects into one. See `PermittedAlphabetConstraint` for more
|
||||||
|
information.
|
||||||
|
"""
|
||||||
|
def _testValue(self, value, idx):
|
||||||
|
for constraint in self._values:
|
||||||
|
try:
|
||||||
|
constraint(value, idx)
|
||||||
|
|
||||||
|
except error.ValueConstraintError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
raise error.ValueConstraintError(value)
|
||||||
|
|
||||||
|
def _setValues(self, values):
|
||||||
|
AbstractConstraint._setValues(self, values)
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractConstraintSet(AbstractConstraint):
|
||||||
|
|
||||||
|
def __getitem__(self, idx):
|
||||||
|
return self._values[idx]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._values)
|
||||||
|
|
||||||
|
def __add__(self, value):
|
||||||
|
return self.__class__(*(self._values + (value,)))
|
||||||
|
|
||||||
|
def __radd__(self, value):
|
||||||
|
return self.__class__(*((value,) + self._values))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._values)
|
||||||
|
|
||||||
|
# Constraints inclusion in sets
|
||||||
|
|
||||||
|
def _setValues(self, values):
|
||||||
|
self._values = values
|
||||||
|
for constraint in values:
|
||||||
|
if constraint:
|
||||||
|
self._valueMap.add(constraint)
|
||||||
|
self._valueMap.update(constraint.getValueMap())
|
||||||
|
|
||||||
|
|
||||||
|
class ConstraintsIntersection(AbstractConstraintSet):
|
||||||
|
"""Create a ConstraintsIntersection logic operator object.
|
||||||
|
|
||||||
|
The ConstraintsIntersection logic operator only succeeds
|
||||||
|
if *all* its operands succeed.
|
||||||
|
|
||||||
|
The ConstraintsIntersection object can be applied to
|
||||||
|
any constraint and logic operator objects.
|
||||||
|
|
||||||
|
The ConstraintsIntersection object duck-types the immutable
|
||||||
|
container object like Python :py:class:`tuple`.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
*constraints:
|
||||||
|
Constraint or logic operator objects.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class CapitalAndSmall(IA5String):
|
||||||
|
'''
|
||||||
|
ASN.1 specification:
|
||||||
|
|
||||||
|
CapitalAndSmall ::=
|
||||||
|
IA5String (FROM ("A".."Z"|"a".."z"))
|
||||||
|
'''
|
||||||
|
subtypeSpec = ConstraintsIntersection(
|
||||||
|
PermittedAlphabetConstraint('A', 'Z'),
|
||||||
|
PermittedAlphabetConstraint('a', 'z')
|
||||||
|
)
|
||||||
|
|
||||||
|
# this will succeed
|
||||||
|
capital_and_small = CapitalAndSmall('Hello')
|
||||||
|
|
||||||
|
# this will raise ValueConstraintError
|
||||||
|
capital_and_small = CapitalAndSmall('hello')
|
||||||
|
"""
|
||||||
|
def _testValue(self, value, idx):
|
||||||
|
for constraint in self._values:
|
||||||
|
constraint(value, idx)
|
||||||
|
|
||||||
|
|
||||||
|
class ConstraintsUnion(AbstractConstraintSet):
|
||||||
|
"""Create a ConstraintsUnion logic operator object.
|
||||||
|
|
||||||
|
The ConstraintsUnion logic operator succeeds if
|
||||||
|
*at least* a single operand succeeds.
|
||||||
|
|
||||||
|
The ConstraintsUnion object can be applied to
|
||||||
|
any constraint and logic operator objects.
|
||||||
|
|
||||||
|
The ConstraintsUnion object duck-types the immutable
|
||||||
|
container object like Python :py:class:`tuple`.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
*constraints:
|
||||||
|
Constraint or logic operator objects.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class CapitalOrSmall(IA5String):
|
||||||
|
'''
|
||||||
|
ASN.1 specification:
|
||||||
|
|
||||||
|
CapitalOrSmall ::=
|
||||||
|
IA5String (FROM ("A".."Z") | FROM ("a".."z"))
|
||||||
|
'''
|
||||||
|
subtypeSpec = ConstraintsUnion(
|
||||||
|
PermittedAlphabetConstraint('A', 'Z'),
|
||||||
|
PermittedAlphabetConstraint('a', 'z')
|
||||||
|
)
|
||||||
|
|
||||||
|
# this will succeed
|
||||||
|
capital_or_small = CapitalAndSmall('Hello')
|
||||||
|
|
||||||
|
# this will raise ValueConstraintError
|
||||||
|
capital_or_small = CapitalOrSmall('hello!')
|
||||||
|
"""
|
||||||
|
def _testValue(self, value, idx):
|
||||||
|
for constraint in self._values:
|
||||||
|
try:
|
||||||
|
constraint(value, idx)
|
||||||
|
except error.ValueConstraintError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
raise error.ValueConstraintError(
|
||||||
|
'all of %s failed for "%s"' % (self._values, value)
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# refactor InnerTypeConstraint
|
||||||
|
# add tests for type check
|
||||||
|
# implement other constraint types
|
||||||
|
# make constraint validation easy to skip
|
||||||
11
pyasn1/type/error.py
Normal file
11
pyasn1/type/error.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
from pyasn1.error import PyAsn1Error
|
||||||
|
|
||||||
|
|
||||||
|
class ValueConstraintError(PyAsn1Error):
|
||||||
|
pass
|
||||||
561
pyasn1/type/namedtype.py
Normal file
561
pyasn1/type/namedtype.py
Normal file
@ -0,0 +1,561 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from pyasn1 import error
|
||||||
|
from pyasn1.type import tag
|
||||||
|
from pyasn1.type import tagmap
|
||||||
|
|
||||||
|
__all__ = ['NamedType', 'OptionalNamedType', 'DefaultedNamedType',
|
||||||
|
'NamedTypes']
|
||||||
|
|
||||||
|
try:
|
||||||
|
any
|
||||||
|
|
||||||
|
except NameError:
|
||||||
|
any = lambda x: bool(filter(bool, x))
|
||||||
|
|
||||||
|
|
||||||
|
class NamedType(object):
|
||||||
|
"""Create named field object for a constructed ASN.1 type.
|
||||||
|
|
||||||
|
The |NamedType| object represents a single name and ASN.1 type of a constructed ASN.1 type.
|
||||||
|
|
||||||
|
|NamedType| objects are immutable and duck-type Python :class:`tuple` objects
|
||||||
|
holding *name* and *asn1Object* components.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
name: :py:class:`str`
|
||||||
|
Field name
|
||||||
|
|
||||||
|
asn1Object:
|
||||||
|
ASN.1 type object
|
||||||
|
"""
|
||||||
|
isOptional = False
|
||||||
|
isDefaulted = False
|
||||||
|
|
||||||
|
def __init__(self, name, asn1Object, openType=None):
|
||||||
|
self.__name = name
|
||||||
|
self.__type = asn1Object
|
||||||
|
self.__nameAndType = name, asn1Object
|
||||||
|
self.__openType = openType
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
representation = '%s=%r' % (self.name, self.asn1Object)
|
||||||
|
|
||||||
|
if self.openType:
|
||||||
|
representation += ', open type %r' % self.openType
|
||||||
|
|
||||||
|
return '<%s object, type %s>' % (
|
||||||
|
self.__class__.__name__, representation)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.__nameAndType == other
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return self.__nameAndType != other
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.__nameAndType < other
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self.__nameAndType <= other
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.__nameAndType > other
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self.__nameAndType >= other
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.__nameAndType)
|
||||||
|
|
||||||
|
def __getitem__(self, idx):
|
||||||
|
return self.__nameAndType[idx]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.__nameAndType)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.__name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def asn1Object(self):
|
||||||
|
return self.__type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def openType(self):
|
||||||
|
return self.__openType
|
||||||
|
|
||||||
|
# Backward compatibility
|
||||||
|
|
||||||
|
def getName(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def getType(self):
|
||||||
|
return self.asn1Object
|
||||||
|
|
||||||
|
|
||||||
|
class OptionalNamedType(NamedType):
|
||||||
|
__doc__ = NamedType.__doc__
|
||||||
|
|
||||||
|
isOptional = True
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultedNamedType(NamedType):
|
||||||
|
__doc__ = NamedType.__doc__
|
||||||
|
|
||||||
|
isDefaulted = True
|
||||||
|
|
||||||
|
|
||||||
|
class NamedTypes(object):
|
||||||
|
"""Create a collection of named fields for a constructed ASN.1 type.
|
||||||
|
|
||||||
|
The NamedTypes object represents a collection of named fields of a constructed ASN.1 type.
|
||||||
|
|
||||||
|
*NamedTypes* objects are immutable and duck-type Python :class:`dict` objects
|
||||||
|
holding *name* as keys and ASN.1 type object as values.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
*namedTypes: :class:`~pyasn1.type.namedtype.NamedType`
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class Description(Sequence):
|
||||||
|
'''
|
||||||
|
ASN.1 specification:
|
||||||
|
|
||||||
|
Description ::= SEQUENCE {
|
||||||
|
surname IA5String,
|
||||||
|
first-name IA5String OPTIONAL,
|
||||||
|
age INTEGER DEFAULT 40
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
componentType = NamedTypes(
|
||||||
|
NamedType('surname', IA5String()),
|
||||||
|
OptionalNamedType('first-name', IA5String()),
|
||||||
|
DefaultedNamedType('age', Integer(40))
|
||||||
|
)
|
||||||
|
|
||||||
|
descr = Description()
|
||||||
|
descr['surname'] = 'Smith'
|
||||||
|
descr['first-name'] = 'John'
|
||||||
|
"""
|
||||||
|
def __init__(self, *namedTypes, **kwargs):
|
||||||
|
self.__namedTypes = namedTypes
|
||||||
|
self.__namedTypesLen = len(self.__namedTypes)
|
||||||
|
self.__minTagSet = self.__computeMinTagSet()
|
||||||
|
self.__nameToPosMap = self.__computeNameToPosMap()
|
||||||
|
self.__tagToPosMap = self.__computeTagToPosMap()
|
||||||
|
self.__ambiguousTypes = 'terminal' not in kwargs and self.__computeAmbiguousTypes() or {}
|
||||||
|
self.__uniqueTagMap = self.__computeTagMaps(unique=True)
|
||||||
|
self.__nonUniqueTagMap = self.__computeTagMaps(unique=False)
|
||||||
|
self.__hasOptionalOrDefault = any([True for namedType in self.__namedTypes
|
||||||
|
if namedType.isDefaulted or namedType.isOptional])
|
||||||
|
self.__hasOpenTypes = any([True for namedType in self.__namedTypes
|
||||||
|
if namedType.openType])
|
||||||
|
|
||||||
|
self.__requiredComponents = frozenset(
|
||||||
|
[idx for idx, nt in enumerate(self.__namedTypes) if not nt.isOptional and not nt.isDefaulted]
|
||||||
|
)
|
||||||
|
self.__keys = frozenset([namedType.name for namedType in self.__namedTypes])
|
||||||
|
self.__values = tuple([namedType.asn1Object for namedType in self.__namedTypes])
|
||||||
|
self.__items = tuple([(namedType.name, namedType.asn1Object) for namedType in self.__namedTypes])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
representation = ', '.join(['%r' % x for x in self.__namedTypes])
|
||||||
|
return '<%s object, types %s>' % (
|
||||||
|
self.__class__.__name__, representation)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.__namedTypes == other
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return self.__namedTypes != other
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.__namedTypes < other
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self.__namedTypes <= other
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.__namedTypes > other
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self.__namedTypes >= other
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.__namedTypes)
|
||||||
|
|
||||||
|
def __getitem__(self, idx):
|
||||||
|
try:
|
||||||
|
return self.__namedTypes[idx]
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
return self.__namedTypes[self.__nameToPosMap[idx]]
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return key in self.__nameToPosMap
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return (x[0] for x in self.__namedTypes)
|
||||||
|
|
||||||
|
if sys.version_info[0] <= 2:
|
||||||
|
def __nonzero__(self):
|
||||||
|
return self.__namedTypesLen > 0
|
||||||
|
else:
|
||||||
|
def __bool__(self):
|
||||||
|
return self.__namedTypesLen > 0
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.__namedTypesLen
|
||||||
|
|
||||||
|
# Python dict protocol
|
||||||
|
|
||||||
|
def values(self):
|
||||||
|
return self.__values
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self.__keys
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return self.__items
|
||||||
|
|
||||||
|
def clone(self):
|
||||||
|
return self.__class__(*self.__namedTypes)
|
||||||
|
|
||||||
|
class PostponedError(object):
|
||||||
|
def __init__(self, errorMsg):
|
||||||
|
self.__errorMsg = errorMsg
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
raise error.PyAsn1Error(self.__errorMsg)
|
||||||
|
|
||||||
|
def __computeTagToPosMap(self):
|
||||||
|
tagToPosMap = {}
|
||||||
|
for idx, namedType in enumerate(self.__namedTypes):
|
||||||
|
tagMap = namedType.asn1Object.tagMap
|
||||||
|
if isinstance(tagMap, NamedTypes.PostponedError):
|
||||||
|
return tagMap
|
||||||
|
if not tagMap:
|
||||||
|
continue
|
||||||
|
for _tagSet in tagMap.presentTypes:
|
||||||
|
if _tagSet in tagToPosMap:
|
||||||
|
return NamedTypes.PostponedError('Duplicate component tag %s at %s' % (_tagSet, namedType))
|
||||||
|
tagToPosMap[_tagSet] = idx
|
||||||
|
|
||||||
|
return tagToPosMap
|
||||||
|
|
||||||
|
def __computeNameToPosMap(self):
|
||||||
|
nameToPosMap = {}
|
||||||
|
for idx, namedType in enumerate(self.__namedTypes):
|
||||||
|
if namedType.name in nameToPosMap:
|
||||||
|
return NamedTypes.PostponedError('Duplicate component name %s at %s' % (namedType.name, namedType))
|
||||||
|
nameToPosMap[namedType.name] = idx
|
||||||
|
|
||||||
|
return nameToPosMap
|
||||||
|
|
||||||
|
def __computeAmbiguousTypes(self):
|
||||||
|
ambiguousTypes = {}
|
||||||
|
partialAmbiguousTypes = ()
|
||||||
|
for idx, namedType in reversed(tuple(enumerate(self.__namedTypes))):
|
||||||
|
if namedType.isOptional or namedType.isDefaulted:
|
||||||
|
partialAmbiguousTypes = (namedType,) + partialAmbiguousTypes
|
||||||
|
else:
|
||||||
|
partialAmbiguousTypes = (namedType,)
|
||||||
|
if len(partialAmbiguousTypes) == len(self.__namedTypes):
|
||||||
|
ambiguousTypes[idx] = self
|
||||||
|
else:
|
||||||
|
ambiguousTypes[idx] = NamedTypes(*partialAmbiguousTypes, **dict(terminal=True))
|
||||||
|
return ambiguousTypes
|
||||||
|
|
||||||
|
def getTypeByPosition(self, idx):
|
||||||
|
"""Return ASN.1 type object by its position in fields set.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
idx: :py:class:`int`
|
||||||
|
Field index
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:
|
||||||
|
ASN.1 type
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
~pyasn1.error.PyAsn1Error
|
||||||
|
If given position is out of fields range
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.__namedTypes[idx].asn1Object
|
||||||
|
|
||||||
|
except IndexError:
|
||||||
|
raise error.PyAsn1Error('Type position out of range')
|
||||||
|
|
||||||
|
def getPositionByType(self, tagSet):
|
||||||
|
"""Return field position by its ASN.1 type.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
tagSet: :class:`~pysnmp.type.tag.TagSet`
|
||||||
|
ASN.1 tag set distinguishing one ASN.1 type from others.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :py:class:`int`
|
||||||
|
ASN.1 type position in fields set
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
~pyasn1.error.PyAsn1Error
|
||||||
|
If *tagSet* is not present or ASN.1 types are not unique within callee *NamedTypes*
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.__tagToPosMap[tagSet]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
raise error.PyAsn1Error('Type %s not found' % (tagSet,))
|
||||||
|
|
||||||
|
def getNameByPosition(self, idx):
|
||||||
|
"""Return field name by its position in fields set.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
idx: :py:class:`idx`
|
||||||
|
Field index
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :py:class:`str`
|
||||||
|
Field name
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
~pyasn1.error.PyAsn1Error
|
||||||
|
If given field name is not present in callee *NamedTypes*
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.__namedTypes[idx].name
|
||||||
|
|
||||||
|
except IndexError:
|
||||||
|
raise error.PyAsn1Error('Type position out of range')
|
||||||
|
|
||||||
|
def getPositionByName(self, name):
|
||||||
|
"""Return field position by filed name.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
name: :py:class:`str`
|
||||||
|
Field name
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :py:class:`int`
|
||||||
|
Field position in fields set
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
~pyasn1.error.PyAsn1Error
|
||||||
|
If *name* is not present or not unique within callee *NamedTypes*
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.__nameToPosMap[name]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
raise error.PyAsn1Error('Name %s not found' % (name,))
|
||||||
|
|
||||||
|
def getTagMapNearPosition(self, idx):
|
||||||
|
"""Return ASN.1 types that are allowed at or past given field position.
|
||||||
|
|
||||||
|
Some ASN.1 serialisation allow for skipping optional and defaulted fields.
|
||||||
|
Some constructed ASN.1 types allow reordering of the fields. When recovering
|
||||||
|
such objects it may be important to know which types can possibly be
|
||||||
|
present at any given position in the field sets.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
idx: :py:class:`int`
|
||||||
|
Field index
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :class:`~pyasn1.type.tagmap.TagMap`
|
||||||
|
Map if ASN.1 types allowed at given field position
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
~pyasn1.error.PyAsn1Error
|
||||||
|
If given position is out of fields range
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.__ambiguousTypes[idx].tagMap
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
raise error.PyAsn1Error('Type position out of range')
|
||||||
|
|
||||||
|
def getPositionNearType(self, tagSet, idx):
|
||||||
|
"""Return the closest field position where given ASN.1 type is allowed.
|
||||||
|
|
||||||
|
Some ASN.1 serialisation allow for skipping optional and defaulted fields.
|
||||||
|
Some constructed ASN.1 types allow reordering of the fields. When recovering
|
||||||
|
such objects it may be important to know at which field position, in field set,
|
||||||
|
given *tagSet* is allowed at or past *idx* position.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
tagSet: :class:`~pyasn1.type.tag.TagSet`
|
||||||
|
ASN.1 type which field position to look up
|
||||||
|
|
||||||
|
idx: :py:class:`int`
|
||||||
|
Field position at or past which to perform ASN.1 type look up
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :py:class:`int`
|
||||||
|
Field position in fields set
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
~pyasn1.error.PyAsn1Error
|
||||||
|
If *tagSet* is not present or not unique within callee *NamedTypes*
|
||||||
|
or *idx* is out of fields range
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return idx + self.__ambiguousTypes[idx].getPositionByType(tagSet)
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
raise error.PyAsn1Error('Type position out of range')
|
||||||
|
|
||||||
|
def __computeMinTagSet(self):
|
||||||
|
minTagSet = None
|
||||||
|
for namedType in self.__namedTypes:
|
||||||
|
asn1Object = namedType.asn1Object
|
||||||
|
|
||||||
|
try:
|
||||||
|
tagSet = asn1Object.minTagSet
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
tagSet = asn1Object.tagSet
|
||||||
|
|
||||||
|
if minTagSet is None or tagSet < minTagSet:
|
||||||
|
minTagSet = tagSet
|
||||||
|
|
||||||
|
return minTagSet or tag.TagSet()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def minTagSet(self):
|
||||||
|
"""Return the minimal TagSet among ASN.1 type in callee *NamedTypes*.
|
||||||
|
|
||||||
|
Some ASN.1 types/serialisation protocols require ASN.1 types to be
|
||||||
|
arranged based on their numerical tag value. The *minTagSet* property
|
||||||
|
returns that.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :class:`~pyasn1.type.tagset.TagSet`
|
||||||
|
Minimal TagSet among ASN.1 types in callee *NamedTypes*
|
||||||
|
"""
|
||||||
|
return self.__minTagSet
|
||||||
|
|
||||||
|
def __computeTagMaps(self, unique):
|
||||||
|
presentTypes = {}
|
||||||
|
skipTypes = {}
|
||||||
|
defaultType = None
|
||||||
|
for namedType in self.__namedTypes:
|
||||||
|
tagMap = namedType.asn1Object.tagMap
|
||||||
|
if isinstance(tagMap, NamedTypes.PostponedError):
|
||||||
|
return tagMap
|
||||||
|
for tagSet in tagMap:
|
||||||
|
if unique and tagSet in presentTypes:
|
||||||
|
return NamedTypes.PostponedError('Non-unique tagSet %s of %s at %s' % (tagSet, namedType, self))
|
||||||
|
presentTypes[tagSet] = namedType.asn1Object
|
||||||
|
skipTypes.update(tagMap.skipTypes)
|
||||||
|
|
||||||
|
if defaultType is None:
|
||||||
|
defaultType = tagMap.defaultType
|
||||||
|
elif tagMap.defaultType is not None:
|
||||||
|
return NamedTypes.PostponedError('Duplicate default ASN.1 type at %s' % (self,))
|
||||||
|
|
||||||
|
return tagmap.TagMap(presentTypes, skipTypes, defaultType)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tagMap(self):
|
||||||
|
"""Return a *TagMap* object from tags and types recursively.
|
||||||
|
|
||||||
|
Return a :class:`~pyasn1.type.tagmap.TagMap` object by
|
||||||
|
combining tags from *TagMap* objects of children types and
|
||||||
|
associating them with their immediate child type.
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
OuterType ::= CHOICE {
|
||||||
|
innerType INTEGER
|
||||||
|
}
|
||||||
|
|
||||||
|
Calling *.tagMap* on *OuterType* will yield a map like this:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
Integer.tagSet -> Choice
|
||||||
|
"""
|
||||||
|
return self.__nonUniqueTagMap
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tagMapUnique(self):
|
||||||
|
"""Return a *TagMap* object from unique tags and types recursively.
|
||||||
|
|
||||||
|
Return a :class:`~pyasn1.type.tagmap.TagMap` object by
|
||||||
|
combining tags from *TagMap* objects of children types and
|
||||||
|
associating them with their immediate child type.
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
OuterType ::= CHOICE {
|
||||||
|
innerType INTEGER
|
||||||
|
}
|
||||||
|
|
||||||
|
Calling *.tagMapUnique* on *OuterType* will yield a map like this:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
Integer.tagSet -> Choice
|
||||||
|
|
||||||
|
Note
|
||||||
|
----
|
||||||
|
|
||||||
|
Duplicate *TagSet* objects found in the tree of children
|
||||||
|
types would cause error.
|
||||||
|
"""
|
||||||
|
return self.__uniqueTagMap
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hasOptionalOrDefault(self):
|
||||||
|
return self.__hasOptionalOrDefault
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hasOpenTypes(self):
|
||||||
|
return self.__hasOpenTypes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def namedTypes(self):
|
||||||
|
return tuple(self.__namedTypes)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def requiredComponents(self):
|
||||||
|
return self.__requiredComponents
|
||||||
192
pyasn1/type/namedval.py
Normal file
192
pyasn1/type/namedval.py
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
# ASN.1 named integers
|
||||||
|
#
|
||||||
|
from pyasn1 import error
|
||||||
|
|
||||||
|
__all__ = ['NamedValues']
|
||||||
|
|
||||||
|
|
||||||
|
class NamedValues(object):
|
||||||
|
"""Create named values object.
|
||||||
|
|
||||||
|
The |NamedValues| object represents a collection of string names
|
||||||
|
associated with numeric IDs. These objects are used for giving
|
||||||
|
names to otherwise numerical values.
|
||||||
|
|
||||||
|
|NamedValues| objects are immutable and duck-type Python
|
||||||
|
:class:`dict` object mapping ID to name and vice-versa.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
*args: variable number of two-element :py:class:`tuple`
|
||||||
|
|
||||||
|
name: :py:class:`str`
|
||||||
|
Value label
|
||||||
|
|
||||||
|
value: :py:class:`int`
|
||||||
|
Numeric value
|
||||||
|
|
||||||
|
Keyword Args
|
||||||
|
------------
|
||||||
|
name: :py:class:`str`
|
||||||
|
Value label
|
||||||
|
|
||||||
|
value: :py:class:`int`
|
||||||
|
Numeric value
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
>>> nv = NamedValues('a', 'b', ('c', 0), d=1)
|
||||||
|
>>> nv
|
||||||
|
>>> {'c': 0, 'd': 1, 'a': 2, 'b': 3}
|
||||||
|
>>> nv[0]
|
||||||
|
'c'
|
||||||
|
>>> nv['a']
|
||||||
|
2
|
||||||
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.__names = {}
|
||||||
|
self.__numbers = {}
|
||||||
|
|
||||||
|
anonymousNames = []
|
||||||
|
|
||||||
|
for namedValue in args:
|
||||||
|
if isinstance(namedValue, (tuple, list)):
|
||||||
|
try:
|
||||||
|
name, number = namedValue
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
raise error.PyAsn1Error('Not a proper attribute-value pair %r' % (namedValue,))
|
||||||
|
|
||||||
|
else:
|
||||||
|
anonymousNames.append(namedValue)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if name in self.__names:
|
||||||
|
raise error.PyAsn1Error('Duplicate name %s' % (name,))
|
||||||
|
|
||||||
|
if number in self.__numbers:
|
||||||
|
raise error.PyAsn1Error('Duplicate number %s=%s' % (name, number))
|
||||||
|
|
||||||
|
self.__names[name] = number
|
||||||
|
self.__numbers[number] = name
|
||||||
|
|
||||||
|
for name, number in kwargs.items():
|
||||||
|
if name in self.__names:
|
||||||
|
raise error.PyAsn1Error('Duplicate name %s' % (name,))
|
||||||
|
|
||||||
|
if number in self.__numbers:
|
||||||
|
raise error.PyAsn1Error('Duplicate number %s=%s' % (name, number))
|
||||||
|
|
||||||
|
self.__names[name] = number
|
||||||
|
self.__numbers[number] = name
|
||||||
|
|
||||||
|
if anonymousNames:
|
||||||
|
|
||||||
|
number = self.__numbers and max(self.__numbers) + 1 or 0
|
||||||
|
|
||||||
|
for name in anonymousNames:
|
||||||
|
|
||||||
|
if name in self.__names:
|
||||||
|
raise error.PyAsn1Error('Duplicate name %s' % (name,))
|
||||||
|
|
||||||
|
self.__names[name] = number
|
||||||
|
self.__numbers[number] = name
|
||||||
|
|
||||||
|
number += 1
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
representation = ', '.join(['%s=%d' % x for x in self.items()])
|
||||||
|
|
||||||
|
if len(representation) > 64:
|
||||||
|
representation = representation[:32] + '...' + representation[-32:]
|
||||||
|
|
||||||
|
return '<%s object, enums %s>' % (
|
||||||
|
self.__class__.__name__, representation)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return dict(self) == other
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return dict(self) != other
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return dict(self) < other
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return dict(self) <= other
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return dict(self) > other
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return dict(self) >= other
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.items())
|
||||||
|
|
||||||
|
# Python dict protocol (read-only)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
try:
|
||||||
|
return self.__numbers[key]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
return self.__names[key]
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.__names)
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return key in self.__names or key in self.__numbers
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.__names)
|
||||||
|
|
||||||
|
def values(self):
|
||||||
|
return iter(self.__numbers)
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return iter(self.__names)
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
for name in self.__names:
|
||||||
|
yield name, self.__names[name]
|
||||||
|
|
||||||
|
# support merging
|
||||||
|
|
||||||
|
def __add__(self, namedValues):
|
||||||
|
return self.__class__(*tuple(self.items()) + tuple(namedValues.items()))
|
||||||
|
|
||||||
|
# XXX clone/subtype?
|
||||||
|
|
||||||
|
def clone(self, *args, **kwargs):
|
||||||
|
new = self.__class__(*args, **kwargs)
|
||||||
|
return self + new
|
||||||
|
|
||||||
|
# legacy protocol
|
||||||
|
|
||||||
|
def getName(self, value):
|
||||||
|
if value in self.__numbers:
|
||||||
|
return self.__numbers[value]
|
||||||
|
|
||||||
|
def getValue(self, name):
|
||||||
|
if name in self.__names:
|
||||||
|
return self.__names[name]
|
||||||
|
|
||||||
|
def getValues(self, *names):
|
||||||
|
try:
|
||||||
|
return [self.__names[name] for name in names]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
raise error.PyAsn1Error(
|
||||||
|
'Unknown bit identifier(s): %s' % (set(names).difference(self.__names),)
|
||||||
|
)
|
||||||
104
pyasn1/type/opentype.py
Normal file
104
pyasn1/type/opentype.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
|
||||||
|
__all__ = ['OpenType']
|
||||||
|
|
||||||
|
|
||||||
|
class OpenType(object):
|
||||||
|
"""Create ASN.1 type map indexed by a value
|
||||||
|
|
||||||
|
The *OpenType* object models an untyped field of a constructed ASN.1
|
||||||
|
type. In ASN.1 syntax it is usually represented by the
|
||||||
|
`ANY DEFINED BY` for scalars or `SET OF ANY DEFINED BY`,
|
||||||
|
`SEQUENCE OF ANY DEFINED BY` for container types clauses. Typically
|
||||||
|
used together with :class:`~pyasn1.type.univ.Any` object.
|
||||||
|
|
||||||
|
OpenType objects duck-type a read-only Python :class:`dict` objects,
|
||||||
|
however the passed `typeMap` is not copied, but stored by reference.
|
||||||
|
That means the user can manipulate `typeMap` at run time having this
|
||||||
|
reflected on *OpenType* object behavior.
|
||||||
|
|
||||||
|
The |OpenType| class models an untyped field of a constructed ASN.1
|
||||||
|
type. In ASN.1 syntax it is usually represented by the
|
||||||
|
`ANY DEFINED BY` for scalars or `SET OF ANY DEFINED BY`,
|
||||||
|
`SEQUENCE OF ANY DEFINED BY` for container types clauses. Typically
|
||||||
|
used with :class:`~pyasn1.type.univ.Any` type.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
name: :py:class:`str`
|
||||||
|
Field name
|
||||||
|
|
||||||
|
typeMap: :py:class:`dict`
|
||||||
|
A map of value->ASN.1 type. It's stored by reference and can be
|
||||||
|
mutated later to register new mappings.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
For untyped scalars:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
openType = OpenType(
|
||||||
|
'id', {1: Integer(),
|
||||||
|
2: OctetString()}
|
||||||
|
)
|
||||||
|
Sequence(
|
||||||
|
componentType=NamedTypes(
|
||||||
|
NamedType('id', Integer()),
|
||||||
|
NamedType('blob', Any(), openType=openType)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
For untyped `SET OF` or `SEQUENCE OF` vectors:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
openType = OpenType(
|
||||||
|
'id', {1: Integer(),
|
||||||
|
2: OctetString()}
|
||||||
|
)
|
||||||
|
Sequence(
|
||||||
|
componentType=NamedTypes(
|
||||||
|
NamedType('id', Integer()),
|
||||||
|
NamedType('blob', SetOf(componentType=Any()),
|
||||||
|
openType=openType)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name, typeMap=None):
|
||||||
|
self.__name = name
|
||||||
|
if typeMap is None:
|
||||||
|
self.__typeMap = {}
|
||||||
|
else:
|
||||||
|
self.__typeMap = typeMap
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.__name
|
||||||
|
|
||||||
|
# Python dict protocol
|
||||||
|
|
||||||
|
def values(self):
|
||||||
|
return self.__typeMap.values()
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self.__typeMap.keys()
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return self.__typeMap.items()
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return key in self.__typeMap
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.__typeMap[key]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.__typeMap)
|
||||||
335
pyasn1/type/tag.py
Normal file
335
pyasn1/type/tag.py
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
from pyasn1 import error
|
||||||
|
|
||||||
|
__all__ = ['tagClassUniversal', 'tagClassApplication', 'tagClassContext',
|
||||||
|
'tagClassPrivate', 'tagFormatSimple', 'tagFormatConstructed',
|
||||||
|
'tagCategoryImplicit', 'tagCategoryExplicit',
|
||||||
|
'tagCategoryUntagged', 'Tag', 'TagSet']
|
||||||
|
|
||||||
|
#: Identifier for ASN.1 class UNIVERSAL
|
||||||
|
tagClassUniversal = 0x00
|
||||||
|
|
||||||
|
#: Identifier for ASN.1 class APPLICATION
|
||||||
|
tagClassApplication = 0x40
|
||||||
|
|
||||||
|
#: Identifier for ASN.1 class context-specific
|
||||||
|
tagClassContext = 0x80
|
||||||
|
|
||||||
|
#: Identifier for ASN.1 class private
|
||||||
|
tagClassPrivate = 0xC0
|
||||||
|
|
||||||
|
#: Identifier for "simple" ASN.1 structure (e.g. scalar)
|
||||||
|
tagFormatSimple = 0x00
|
||||||
|
|
||||||
|
#: Identifier for "constructed" ASN.1 structure (e.g. may have inner components)
|
||||||
|
tagFormatConstructed = 0x20
|
||||||
|
|
||||||
|
tagCategoryImplicit = 0x01
|
||||||
|
tagCategoryExplicit = 0x02
|
||||||
|
tagCategoryUntagged = 0x04
|
||||||
|
|
||||||
|
|
||||||
|
class Tag(object):
|
||||||
|
"""Create ASN.1 tag
|
||||||
|
|
||||||
|
Represents ASN.1 tag that can be attached to a ASN.1 type to make
|
||||||
|
types distinguishable from each other.
|
||||||
|
|
||||||
|
*Tag* objects are immutable and duck-type Python :class:`tuple` objects
|
||||||
|
holding three integer components of a tag.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
tagClass: :py:class:`int`
|
||||||
|
Tag *class* value
|
||||||
|
|
||||||
|
tagFormat: :py:class:`int`
|
||||||
|
Tag *format* value
|
||||||
|
|
||||||
|
tagId: :py:class:`int`
|
||||||
|
Tag ID value
|
||||||
|
"""
|
||||||
|
def __init__(self, tagClass, tagFormat, tagId):
|
||||||
|
if tagId < 0:
|
||||||
|
raise error.PyAsn1Error('Negative tag ID (%s) not allowed' % tagId)
|
||||||
|
self.__tagClass = tagClass
|
||||||
|
self.__tagFormat = tagFormat
|
||||||
|
self.__tagId = tagId
|
||||||
|
self.__tagClassId = tagClass, tagId
|
||||||
|
self.__hash = hash(self.__tagClassId)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
representation = '[%s:%s:%s]' % (
|
||||||
|
self.__tagClass, self.__tagFormat, self.__tagId)
|
||||||
|
return '<%s object, tag %s>' % (
|
||||||
|
self.__class__.__name__, representation)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.__tagClassId == other
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return self.__tagClassId != other
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.__tagClassId < other
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self.__tagClassId <= other
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.__tagClassId > other
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self.__tagClassId >= other
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return self.__hash
|
||||||
|
|
||||||
|
def __getitem__(self, idx):
|
||||||
|
if idx == 0:
|
||||||
|
return self.__tagClass
|
||||||
|
elif idx == 1:
|
||||||
|
return self.__tagFormat
|
||||||
|
elif idx == 2:
|
||||||
|
return self.__tagId
|
||||||
|
else:
|
||||||
|
raise IndexError()
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
yield self.__tagClass
|
||||||
|
yield self.__tagFormat
|
||||||
|
yield self.__tagId
|
||||||
|
|
||||||
|
def __and__(self, otherTag):
|
||||||
|
return self.__class__(self.__tagClass & otherTag.tagClass,
|
||||||
|
self.__tagFormat & otherTag.tagFormat,
|
||||||
|
self.__tagId & otherTag.tagId)
|
||||||
|
|
||||||
|
def __or__(self, otherTag):
|
||||||
|
return self.__class__(self.__tagClass | otherTag.tagClass,
|
||||||
|
self.__tagFormat | otherTag.tagFormat,
|
||||||
|
self.__tagId | otherTag.tagId)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tagClass(self):
|
||||||
|
"""ASN.1 tag class
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :py:class:`int`
|
||||||
|
Tag class
|
||||||
|
"""
|
||||||
|
return self.__tagClass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tagFormat(self):
|
||||||
|
"""ASN.1 tag format
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :py:class:`int`
|
||||||
|
Tag format
|
||||||
|
"""
|
||||||
|
return self.__tagFormat
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tagId(self):
|
||||||
|
"""ASN.1 tag ID
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :py:class:`int`
|
||||||
|
Tag ID
|
||||||
|
"""
|
||||||
|
return self.__tagId
|
||||||
|
|
||||||
|
|
||||||
|
class TagSet(object):
|
||||||
|
"""Create a collection of ASN.1 tags
|
||||||
|
|
||||||
|
Represents a combination of :class:`~pyasn1.type.tag.Tag` objects
|
||||||
|
that can be attached to a ASN.1 type to make types distinguishable
|
||||||
|
from each other.
|
||||||
|
|
||||||
|
*TagSet* objects are immutable and duck-type Python :class:`tuple` objects
|
||||||
|
holding arbitrary number of :class:`~pyasn1.type.tag.Tag` objects.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
baseTag: :class:`~pyasn1.type.tag.Tag`
|
||||||
|
Base *Tag* object. This tag survives IMPLICIT tagging.
|
||||||
|
|
||||||
|
*superTags: :class:`~pyasn1.type.tag.Tag`
|
||||||
|
Additional *Tag* objects taking part in subtyping.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class OrderNumber(NumericString):
|
||||||
|
'''
|
||||||
|
ASN.1 specification
|
||||||
|
|
||||||
|
Order-number ::=
|
||||||
|
[APPLICATION 5] IMPLICIT NumericString
|
||||||
|
'''
|
||||||
|
tagSet = NumericString.tagSet.tagImplicitly(
|
||||||
|
Tag(tagClassApplication, tagFormatSimple, 5)
|
||||||
|
)
|
||||||
|
|
||||||
|
orderNumber = OrderNumber('1234')
|
||||||
|
"""
|
||||||
|
def __init__(self, baseTag=(), *superTags):
|
||||||
|
self.__baseTag = baseTag
|
||||||
|
self.__superTags = superTags
|
||||||
|
self.__superTagsClassId = tuple(
|
||||||
|
[(superTag.tagClass, superTag.tagId) for superTag in superTags]
|
||||||
|
)
|
||||||
|
self.__lenOfSuperTags = len(superTags)
|
||||||
|
self.__hash = hash(self.__superTagsClassId)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
representation = '-'.join(['%s:%s:%s' % (x.tagClass, x.tagFormat, x.tagId)
|
||||||
|
for x in self.__superTags])
|
||||||
|
if representation:
|
||||||
|
representation = 'tags ' + representation
|
||||||
|
else:
|
||||||
|
representation = 'untagged'
|
||||||
|
|
||||||
|
return '<%s object, %s>' % (self.__class__.__name__, representation)
|
||||||
|
|
||||||
|
def __add__(self, superTag):
|
||||||
|
return self.__class__(self.__baseTag, *self.__superTags + (superTag,))
|
||||||
|
|
||||||
|
def __radd__(self, superTag):
|
||||||
|
return self.__class__(self.__baseTag, *(superTag,) + self.__superTags)
|
||||||
|
|
||||||
|
def __getitem__(self, i):
|
||||||
|
if i.__class__ is slice:
|
||||||
|
return self.__class__(self.__baseTag, *self.__superTags[i])
|
||||||
|
else:
|
||||||
|
return self.__superTags[i]
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.__superTagsClassId == other
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return self.__superTagsClassId != other
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.__superTagsClassId < other
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self.__superTagsClassId <= other
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.__superTagsClassId > other
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self.__superTagsClassId >= other
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return self.__hash
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.__lenOfSuperTags
|
||||||
|
|
||||||
|
@property
|
||||||
|
def baseTag(self):
|
||||||
|
"""Return base ASN.1 tag
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :class:`~pyasn1.type.tag.Tag`
|
||||||
|
Base tag of this *TagSet*
|
||||||
|
"""
|
||||||
|
return self.__baseTag
|
||||||
|
|
||||||
|
@property
|
||||||
|
def superTags(self):
|
||||||
|
"""Return ASN.1 tags
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :py:class:`tuple`
|
||||||
|
Tuple of :class:`~pyasn1.type.tag.Tag` objects that this *TagSet* contains
|
||||||
|
"""
|
||||||
|
return self.__superTags
|
||||||
|
|
||||||
|
def tagExplicitly(self, superTag):
|
||||||
|
"""Return explicitly tagged *TagSet*
|
||||||
|
|
||||||
|
Create a new *TagSet* representing callee *TagSet* explicitly tagged
|
||||||
|
with passed tag(s). With explicit tagging mode, new tags are appended
|
||||||
|
to existing tag(s).
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
superTag: :class:`~pyasn1.type.tag.Tag`
|
||||||
|
*Tag* object to tag this *TagSet*
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :class:`~pyasn1.type.tag.TagSet`
|
||||||
|
New *TagSet* object
|
||||||
|
"""
|
||||||
|
if superTag.tagClass == tagClassUniversal:
|
||||||
|
raise error.PyAsn1Error("Can't tag with UNIVERSAL class tag")
|
||||||
|
if superTag.tagFormat != tagFormatConstructed:
|
||||||
|
superTag = Tag(superTag.tagClass, tagFormatConstructed, superTag.tagId)
|
||||||
|
return self + superTag
|
||||||
|
|
||||||
|
def tagImplicitly(self, superTag):
|
||||||
|
"""Return implicitly tagged *TagSet*
|
||||||
|
|
||||||
|
Create a new *TagSet* representing callee *TagSet* implicitly tagged
|
||||||
|
with passed tag(s). With implicit tagging mode, new tag(s) replace the
|
||||||
|
last existing tag.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
superTag: :class:`~pyasn1.type.tag.Tag`
|
||||||
|
*Tag* object to tag this *TagSet*
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :class:`~pyasn1.type.tag.TagSet`
|
||||||
|
New *TagSet* object
|
||||||
|
"""
|
||||||
|
if self.__superTags:
|
||||||
|
superTag = Tag(superTag.tagClass, self.__superTags[-1].tagFormat, superTag.tagId)
|
||||||
|
return self[:-1] + superTag
|
||||||
|
|
||||||
|
def isSuperTagSetOf(self, tagSet):
|
||||||
|
"""Test type relationship against given *TagSet*
|
||||||
|
|
||||||
|
The callee is considered to be a supertype of given *TagSet*
|
||||||
|
tag-wise if all tags in *TagSet* are present in the callee and
|
||||||
|
they are in the same order.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
tagSet: :class:`~pyasn1.type.tag.TagSet`
|
||||||
|
*TagSet* object to evaluate against the callee
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
: :py:class:`bool`
|
||||||
|
:obj:`True` if callee is a supertype of *tagSet*
|
||||||
|
"""
|
||||||
|
if len(tagSet) < self.__lenOfSuperTags:
|
||||||
|
return False
|
||||||
|
return self.__superTags == tagSet[:self.__lenOfSuperTags]
|
||||||
|
|
||||||
|
# Backward compatibility
|
||||||
|
|
||||||
|
def getBaseTag(self):
|
||||||
|
return self.__baseTag
|
||||||
|
|
||||||
|
def initTagSet(tag):
|
||||||
|
return TagSet(tag, tag)
|
||||||
96
pyasn1/type/tagmap.py
Normal file
96
pyasn1/type/tagmap.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
from pyasn1 import error
|
||||||
|
|
||||||
|
__all__ = ['TagMap']
|
||||||
|
|
||||||
|
|
||||||
|
class TagMap(object):
|
||||||
|
"""Map *TagSet* objects to ASN.1 types
|
||||||
|
|
||||||
|
Create an object mapping *TagSet* object to ASN.1 type.
|
||||||
|
|
||||||
|
*TagMap* objects are immutable and duck-type read-only Python
|
||||||
|
:class:`dict` objects holding *TagSet* objects as keys and ASN.1
|
||||||
|
type objects as values.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
presentTypes: :py:class:`dict`
|
||||||
|
Map of :class:`~pyasn1.type.tag.TagSet` to ASN.1 objects considered
|
||||||
|
as being unconditionally present in the *TagMap*.
|
||||||
|
|
||||||
|
skipTypes: :py:class:`dict`
|
||||||
|
A collection of :class:`~pyasn1.type.tag.TagSet` objects considered
|
||||||
|
as absent in the *TagMap* even when *defaultType* is present.
|
||||||
|
|
||||||
|
defaultType: ASN.1 type object
|
||||||
|
An ASN.1 type object callee *TagMap* returns for any *TagSet* key not present
|
||||||
|
in *presentTypes* (unless given key is present in *skipTypes*).
|
||||||
|
"""
|
||||||
|
def __init__(self, presentTypes=None, skipTypes=None, defaultType=None):
|
||||||
|
self.__presentTypes = presentTypes or {}
|
||||||
|
self.__skipTypes = skipTypes or {}
|
||||||
|
self.__defaultType = defaultType
|
||||||
|
|
||||||
|
def __contains__(self, tagSet):
|
||||||
|
return (tagSet in self.__presentTypes or
|
||||||
|
self.__defaultType is not None and tagSet not in self.__skipTypes)
|
||||||
|
|
||||||
|
def __getitem__(self, tagSet):
|
||||||
|
try:
|
||||||
|
return self.__presentTypes[tagSet]
|
||||||
|
except KeyError:
|
||||||
|
if self.__defaultType is None:
|
||||||
|
raise KeyError()
|
||||||
|
elif tagSet in self.__skipTypes:
|
||||||
|
raise error.PyAsn1Error('Key in negative map')
|
||||||
|
else:
|
||||||
|
return self.__defaultType
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.__presentTypes)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
representation = '%s object' % self.__class__.__name__
|
||||||
|
|
||||||
|
if self.__presentTypes:
|
||||||
|
representation += ', present %s' % repr(self.__presentTypes)
|
||||||
|
|
||||||
|
if self.__skipTypes:
|
||||||
|
representation += ', skip %s' % repr(self.__skipTypes)
|
||||||
|
|
||||||
|
if self.__defaultType is not None:
|
||||||
|
representation += ', default %s' % repr(self.__defaultType)
|
||||||
|
|
||||||
|
return '<%s>' % representation
|
||||||
|
|
||||||
|
@property
|
||||||
|
def presentTypes(self):
|
||||||
|
"""Return *TagSet* to ASN.1 type map present in callee *TagMap*"""
|
||||||
|
return self.__presentTypes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def skipTypes(self):
|
||||||
|
"""Return *TagSet* collection unconditionally absent in callee *TagMap*"""
|
||||||
|
return self.__skipTypes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def defaultType(self):
|
||||||
|
"""Return default ASN.1 type being returned for any missing *TagSet*"""
|
||||||
|
return self.__defaultType
|
||||||
|
|
||||||
|
# Backward compatibility
|
||||||
|
|
||||||
|
def getPosMap(self):
|
||||||
|
return self.presentTypes
|
||||||
|
|
||||||
|
def getNegMap(self):
|
||||||
|
return self.skipTypes
|
||||||
|
|
||||||
|
def getDef(self):
|
||||||
|
return self.defaultType
|
||||||
3321
pyasn1/type/univ.py
Normal file
3321
pyasn1/type/univ.py
Normal file
File diff suppressed because it is too large
Load Diff
191
pyasn1/type/useful.py
Normal file
191
pyasn1/type/useful.py
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pyasn1 software.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
|
||||||
|
# License: http://snmplabs.com/pyasn1/license.html
|
||||||
|
#
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from pyasn1 import error
|
||||||
|
from pyasn1.compat import dateandtime
|
||||||
|
from pyasn1.compat import string
|
||||||
|
from pyasn1.type import char
|
||||||
|
from pyasn1.type import tag
|
||||||
|
from pyasn1.type import univ
|
||||||
|
|
||||||
|
__all__ = ['ObjectDescriptor', 'GeneralizedTime', 'UTCTime']
|
||||||
|
|
||||||
|
NoValue = univ.NoValue
|
||||||
|
noValue = univ.noValue
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectDescriptor(char.GraphicString):
|
||||||
|
__doc__ = char.GraphicString.__doc__
|
||||||
|
|
||||||
|
#: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects
|
||||||
|
tagSet = char.GraphicString.tagSet.tagImplicitly(
|
||||||
|
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 7)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Optimization for faster codec lookup
|
||||||
|
typeId = char.GraphicString.getTypeId()
|
||||||
|
|
||||||
|
|
||||||
|
class TimeMixIn(object):
|
||||||
|
|
||||||
|
_yearsDigits = 4
|
||||||
|
_hasSubsecond = False
|
||||||
|
_optionalMinutes = False
|
||||||
|
_shortTZ = False
|
||||||
|
|
||||||
|
class FixedOffset(datetime.tzinfo):
|
||||||
|
"""Fixed offset in minutes east from UTC."""
|
||||||
|
|
||||||
|
# defaulted arguments required
|
||||||
|
# https: // docs.python.org / 2.3 / lib / datetime - tzinfo.html
|
||||||
|
def __init__(self, offset=0, name='UTC'):
|
||||||
|
self.__offset = datetime.timedelta(minutes=offset)
|
||||||
|
self.__name = name
|
||||||
|
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
return self.__offset
|
||||||
|
|
||||||
|
def tzname(self, dt):
|
||||||
|
return self.__name
|
||||||
|
|
||||||
|
def dst(self, dt):
|
||||||
|
return datetime.timedelta(0)
|
||||||
|
|
||||||
|
UTC = FixedOffset()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def asDateTime(self):
|
||||||
|
"""Create :py:class:`datetime.datetime` object from a |ASN.1| object.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:
|
||||||
|
new instance of :py:class:`datetime.datetime` object
|
||||||
|
"""
|
||||||
|
text = str(self)
|
||||||
|
if text.endswith('Z'):
|
||||||
|
tzinfo = TimeMixIn.UTC
|
||||||
|
text = text[:-1]
|
||||||
|
|
||||||
|
elif '-' in text or '+' in text:
|
||||||
|
if '+' in text:
|
||||||
|
text, plusminus, tz = string.partition(text, '+')
|
||||||
|
else:
|
||||||
|
text, plusminus, tz = string.partition(text, '-')
|
||||||
|
|
||||||
|
if self._shortTZ and len(tz) == 2:
|
||||||
|
tz += '00'
|
||||||
|
|
||||||
|
if len(tz) != 4:
|
||||||
|
raise error.PyAsn1Error('malformed time zone offset %s' % tz)
|
||||||
|
|
||||||
|
try:
|
||||||
|
minutes = int(tz[:2]) * 60 + int(tz[2:])
|
||||||
|
if plusminus == '-':
|
||||||
|
minutes *= -1
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
raise error.PyAsn1Error('unknown time specification %s' % self)
|
||||||
|
|
||||||
|
tzinfo = TimeMixIn.FixedOffset(minutes, '?')
|
||||||
|
|
||||||
|
else:
|
||||||
|
tzinfo = None
|
||||||
|
|
||||||
|
if '.' in text or ',' in text:
|
||||||
|
if '.' in text:
|
||||||
|
text, _, ms = string.partition(text, '.')
|
||||||
|
else:
|
||||||
|
text, _, ms = string.partition(text, ',')
|
||||||
|
|
||||||
|
try:
|
||||||
|
ms = int(ms) * 1000
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
raise error.PyAsn1Error('bad sub-second time specification %s' % self)
|
||||||
|
|
||||||
|
else:
|
||||||
|
ms = 0
|
||||||
|
|
||||||
|
if self._optionalMinutes and len(text) - self._yearsDigits == 6:
|
||||||
|
text += '0000'
|
||||||
|
elif len(text) - self._yearsDigits == 8:
|
||||||
|
text += '00'
|
||||||
|
|
||||||
|
try:
|
||||||
|
dt = dateandtime.strptime(text, self._yearsDigits == 4 and '%Y%m%d%H%M%S' or '%y%m%d%H%M%S')
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
raise error.PyAsn1Error('malformed datetime format %s' % self)
|
||||||
|
|
||||||
|
return dt.replace(microsecond=ms, tzinfo=tzinfo)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fromDateTime(cls, dt):
|
||||||
|
"""Create |ASN.1| object from a :py:class:`datetime.datetime` object.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
dt: :py:class:`datetime.datetime` object
|
||||||
|
The `datetime.datetime` object to initialize the |ASN.1| object
|
||||||
|
from
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:
|
||||||
|
new instance of |ASN.1| value
|
||||||
|
"""
|
||||||
|
text = dt.strftime(cls._yearsDigits == 4 and '%Y%m%d%H%M%S' or '%y%m%d%H%M%S')
|
||||||
|
if cls._hasSubsecond:
|
||||||
|
text += '.%d' % (dt.microsecond // 1000)
|
||||||
|
|
||||||
|
if dt.utcoffset():
|
||||||
|
seconds = dt.utcoffset().seconds
|
||||||
|
if seconds < 0:
|
||||||
|
text += '-'
|
||||||
|
else:
|
||||||
|
text += '+'
|
||||||
|
text += '%.2d%.2d' % (seconds // 3600, seconds % 3600)
|
||||||
|
else:
|
||||||
|
text += 'Z'
|
||||||
|
|
||||||
|
return cls(text)
|
||||||
|
|
||||||
|
|
||||||
|
class GeneralizedTime(char.VisibleString, TimeMixIn):
|
||||||
|
__doc__ = char.VisibleString.__doc__
|
||||||
|
|
||||||
|
#: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects
|
||||||
|
tagSet = char.VisibleString.tagSet.tagImplicitly(
|
||||||
|
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 24)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Optimization for faster codec lookup
|
||||||
|
typeId = char.VideotexString.getTypeId()
|
||||||
|
|
||||||
|
_yearsDigits = 4
|
||||||
|
_hasSubsecond = True
|
||||||
|
_optionalMinutes = True
|
||||||
|
_shortTZ = True
|
||||||
|
|
||||||
|
|
||||||
|
class UTCTime(char.VisibleString, TimeMixIn):
|
||||||
|
__doc__ = char.VisibleString.__doc__
|
||||||
|
|
||||||
|
#: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects
|
||||||
|
tagSet = char.VisibleString.tagSet.tagImplicitly(
|
||||||
|
tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 23)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Optimization for faster codec lookup
|
||||||
|
typeId = char.VideotexString.getTypeId()
|
||||||
|
|
||||||
|
_yearsDigits = 2
|
||||||
|
_hasSubsecond = False
|
||||||
|
_optionalMinutes = False
|
||||||
|
_shortTZ = False
|
||||||
7
requirements.txt
Normal file
7
requirements.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
certifi==2020.12.5
|
||||||
|
chardet==4.0.0
|
||||||
|
idna==2.10
|
||||||
|
pyasn1==0.4.8
|
||||||
|
requests==2.25.1
|
||||||
|
rsa==4.7.2
|
||||||
|
urllib3==1.26.4
|
||||||
1
rsa-4.7.2.dist-info/INSTALLER
Normal file
1
rsa-4.7.2.dist-info/INSTALLER
Normal file
@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
13
rsa-4.7.2.dist-info/LICENSE
Normal file
13
rsa-4.7.2.dist-info/LICENSE
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
87
rsa-4.7.2.dist-info/METADATA
Normal file
87
rsa-4.7.2.dist-info/METADATA
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: rsa
|
||||||
|
Version: 4.7.2
|
||||||
|
Summary: Pure-Python RSA implementation
|
||||||
|
Home-page: https://stuvel.eu/rsa
|
||||||
|
Author: Sybren A. Stuvel
|
||||||
|
Author-email: sybren@stuvel.eu
|
||||||
|
Maintainer: Sybren A. Stuvel
|
||||||
|
Maintainer-email: sybren@stuvel.eu
|
||||||
|
License: ASL 2
|
||||||
|
Platform: UNKNOWN
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: Intended Audience :: Education
|
||||||
|
Classifier: Intended Audience :: Information Technology
|
||||||
|
Classifier: License :: OSI Approved :: Apache Software License
|
||||||
|
Classifier: Operating System :: OS Independent
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python :: 3.5
|
||||||
|
Classifier: Programming Language :: Python :: 3.6
|
||||||
|
Classifier: Programming Language :: Python :: 3.7
|
||||||
|
Classifier: Programming Language :: Python :: 3.8
|
||||||
|
Classifier: Programming Language :: Python :: 3.9
|
||||||
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||||
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||||
|
Classifier: Topic :: Security :: Cryptography
|
||||||
|
Requires-Python: >=3.5, <4
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
Requires-Dist: pyasn1 (>=0.1.3)
|
||||||
|
|
||||||
|
Pure Python RSA implementation
|
||||||
|
==============================
|
||||||
|
|
||||||
|
[](https://pypi.org/project/rsa/)
|
||||||
|
[](https://travis-ci.org/sybrenstuvel/python-rsa)
|
||||||
|
[](https://coveralls.io/github/sybrenstuvel/python-rsa?branch=master)
|
||||||
|
[](https://codeclimate.com/github/codeclimate/codeclimate/maintainability)
|
||||||
|
|
||||||
|
[Python-RSA](https://stuvel.eu/rsa) is a pure-Python RSA implementation. It supports
|
||||||
|
encryption and decryption, signing and verifying signatures, and key
|
||||||
|
generation according to PKCS#1 version 1.5. It can be used as a Python
|
||||||
|
library as well as on the commandline. The code was mostly written by
|
||||||
|
Sybren A. Stüvel.
|
||||||
|
|
||||||
|
Documentation can be found at the [Python-RSA homepage](https://stuvel.eu/rsa). For all changes, check [the changelog](https://github.com/sybrenstuvel/python-rsa/blob/master/CHANGELOG.md).
|
||||||
|
|
||||||
|
Download and install using:
|
||||||
|
|
||||||
|
pip install rsa
|
||||||
|
|
||||||
|
or download it from the [Python Package Index](https://pypi.org/project/rsa/).
|
||||||
|
|
||||||
|
The source code is maintained at [GitHub](https://github.com/sybrenstuvel/python-rsa/) and is
|
||||||
|
licensed under the [Apache License, version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
|
||||||
|
Security
|
||||||
|
--------
|
||||||
|
|
||||||
|
Because of how Python internally stores numbers, it is very hard (if not impossible) to make a pure-Python program secure against timing attacks. This library is no exception, so use it with care. See https://securitypitfalls.wordpress.com/2018/08/03/constant-time-compare-in-python/ for more info.
|
||||||
|
|
||||||
|
|
||||||
|
Major changes in 4.1
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Version 4.0 was the last version to support Python 2 and 3.4. Version 4.1 is compatible with Python 3.5+ only.
|
||||||
|
|
||||||
|
|
||||||
|
Major changes in 4.0
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Version 3.4 was the last version in the 3.x range. Version 4.0 drops the following modules,
|
||||||
|
as they are insecure:
|
||||||
|
|
||||||
|
- `rsa._version133`
|
||||||
|
- `rsa._version200`
|
||||||
|
- `rsa.bigfile`
|
||||||
|
- `rsa.varblock`
|
||||||
|
|
||||||
|
Those modules were marked as deprecated in version 3.4.
|
||||||
|
|
||||||
|
Furthermore, in 4.0 the I/O functions is streamlined to always work with bytes on all
|
||||||
|
supported versions of Python.
|
||||||
|
|
||||||
|
Version 4.0 drops support for Python 2.6 and 3.3.
|
||||||
|
|
||||||
|
|
||||||
44
rsa-4.7.2.dist-info/RECORD
Normal file
44
rsa-4.7.2.dist-info/RECORD
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
../../bin/pyrsa-decrypt.exe,sha256=ztycggZFMPsf7bOruIfQbYhN9msc9xrESHQhM7Ne4iY,106364
|
||||||
|
../../bin/pyrsa-encrypt.exe,sha256=SMwrLJFMpxA0diiVB0AotYkOVn5iiJH5i0KtqPddS7I,106364
|
||||||
|
../../bin/pyrsa-keygen.exe,sha256=tEMummBrsa9PQH9Xam7iYbFKErDQnRXXecZEdJ_oW3o,106362
|
||||||
|
../../bin/pyrsa-priv2pub.exe,sha256=uo3cc7QcvsTr3GTx7NmhVVzmViKzm2OoFx0z8BvM60g,106385
|
||||||
|
../../bin/pyrsa-sign.exe,sha256=52ilIsciptZQdIEiMHKKiOePsLhVvWlbn4dzgC4UiO8,106358
|
||||||
|
../../bin/pyrsa-verify.exe,sha256=n746wcOfwANOCOM3_q87oLL0gZm-EZfe9acZY7PYd2Y,106362
|
||||||
|
rsa-4.7.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
rsa-4.7.2.dist-info/LICENSE,sha256=Bz8ot9OJyP509gfhfCf4HqpazmntxDqITyP0G0HFxyY,577
|
||||||
|
rsa-4.7.2.dist-info/METADATA,sha256=_C4p6B8Rwf363bv8EHxBZtIjpuOHHpjWgcU2_vaalkM,3580
|
||||||
|
rsa-4.7.2.dist-info/RECORD,,
|
||||||
|
rsa-4.7.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
rsa-4.7.2.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
|
||||||
|
rsa-4.7.2.dist-info/entry_points.txt,sha256=CfbS6zx6jc_obqg6SQDd9J0QdoGYDMKFosDQ9ADn_Pg,213
|
||||||
|
rsa-4.7.2.dist-info/top_level.txt,sha256=fAq1Bm-GX3Blvuu7bvG87v7ye2ZJoIL7oVz1F9FdxjI,4
|
||||||
|
rsa/__init__.py,sha256=E3Apjlif-MA314xa9R66g1y1jYD2xpONRqaxC9IbK3s,1544
|
||||||
|
rsa/__pycache__/__init__.cpython-39.pyc,,
|
||||||
|
rsa/__pycache__/_compat.cpython-39.pyc,,
|
||||||
|
rsa/__pycache__/asn1.cpython-39.pyc,,
|
||||||
|
rsa/__pycache__/cli.cpython-39.pyc,,
|
||||||
|
rsa/__pycache__/common.cpython-39.pyc,,
|
||||||
|
rsa/__pycache__/core.cpython-39.pyc,,
|
||||||
|
rsa/__pycache__/key.cpython-39.pyc,,
|
||||||
|
rsa/__pycache__/parallel.cpython-39.pyc,,
|
||||||
|
rsa/__pycache__/pem.cpython-39.pyc,,
|
||||||
|
rsa/__pycache__/pkcs1.cpython-39.pyc,,
|
||||||
|
rsa/__pycache__/pkcs1_v2.cpython-39.pyc,,
|
||||||
|
rsa/__pycache__/prime.cpython-39.pyc,,
|
||||||
|
rsa/__pycache__/randnum.cpython-39.pyc,,
|
||||||
|
rsa/__pycache__/transform.cpython-39.pyc,,
|
||||||
|
rsa/__pycache__/util.cpython-39.pyc,,
|
||||||
|
rsa/_compat.py,sha256=TxeP2UMfzzBLIonvsOAJeUjGUss5Jqzxf_OBZ_-U55U,1486
|
||||||
|
rsa/asn1.py,sha256=PATmCQA8q4ACEQthh7PQJ_eBSM-hd9kJe8AvgcBSVk8,1755
|
||||||
|
rsa/cli.py,sha256=5XlXri_r9KB42M434UPs_Zr07ara2Reko2lGSlD7KPs,9971
|
||||||
|
rsa/common.py,sha256=wP3ipCBTx1-Kuua8sMsH8LzkIxE8OoCz0gLAjvJw0NY,4682
|
||||||
|
rsa/core.py,sha256=jdLgt5PKLgwxlzJTtcQf-pFoaOVBuTkW-Qbe6yI-ZZk,1661
|
||||||
|
rsa/key.py,sha256=-RyjRNsxhPeFCTPGNwkgyrXjL_7ZPd1kO-x1Ylidrb0,27224
|
||||||
|
rsa/parallel.py,sha256=BWa9xCqj90CmjUsVSFcZ6cDGNeF5BVUWBj3lWmQYJYs,2326
|
||||||
|
rsa/pem.py,sha256=9B4KYI7aPAayCbXOAeDz4flWJouaf8Io9_dItJbwN7k,3976
|
||||||
|
rsa/pkcs1.py,sha256=QWF79XbPtHTlsq-dVDEgVTgrkxLenFTkCmnTsmLmxf8,16046
|
||||||
|
rsa/pkcs1_v2.py,sha256=WlA9vbvyjpHYmK7sra3Rvk-ZDxoPk88FYH9gDgH1OII,3433
|
||||||
|
rsa/prime.py,sha256=Ba6twrpKQofhczYORy08HXj02J-6pOSg1esiNQYwctM,5105
|
||||||
|
rsa/randnum.py,sha256=5w2v2i3uUYT7lPksY__L6GODflVoFAgCqVLZTM7Mu8I,2664
|
||||||
|
rsa/transform.py,sha256=OA26lxSlY5VmQwQc7g8IMuu8WE5VVQp8FLQ2NCMp-wQ,2200
|
||||||
|
rsa/util.py,sha256=REoqdewjl0_pzxO6RJrZt8haVMeZbtq3bckpWLwFMcw,2986
|
||||||
0
rsa-4.7.2.dist-info/REQUESTED
Normal file
0
rsa-4.7.2.dist-info/REQUESTED
Normal file
5
rsa-4.7.2.dist-info/WHEEL
Normal file
5
rsa-4.7.2.dist-info/WHEEL
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: bdist_wheel (0.36.2)
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py3-none-any
|
||||||
|
|
||||||
8
rsa-4.7.2.dist-info/entry_points.txt
Normal file
8
rsa-4.7.2.dist-info/entry_points.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[console_scripts]
|
||||||
|
pyrsa-decrypt = rsa.cli:decrypt
|
||||||
|
pyrsa-encrypt = rsa.cli:encrypt
|
||||||
|
pyrsa-keygen = rsa.cli:keygen
|
||||||
|
pyrsa-priv2pub = rsa.util:private_to_public
|
||||||
|
pyrsa-sign = rsa.cli:sign
|
||||||
|
pyrsa-verify = rsa.cli:verify
|
||||||
|
|
||||||
1
rsa-4.7.2.dist-info/top_level.txt
Normal file
1
rsa-4.7.2.dist-info/top_level.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
rsa
|
||||||
40
rsa/__init__.py
Normal file
40
rsa/__init__.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
"""RSA module
|
||||||
|
|
||||||
|
Module for calculating large primes, and RSA encryption, decryption, signing
|
||||||
|
and verification. Includes generating public and private keys.
|
||||||
|
|
||||||
|
WARNING: this implementation does not use compression of the cleartext input to
|
||||||
|
prevent repetitions, or other common security improvements. Use with care.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from rsa.key import newkeys, PrivateKey, PublicKey
|
||||||
|
from rsa.pkcs1 import encrypt, decrypt, sign, verify, DecryptionError, \
|
||||||
|
VerificationError, find_signature_hash, sign_hash, compute_hash
|
||||||
|
|
||||||
|
__author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly"
|
||||||
|
__date__ = '2021-02-24'
|
||||||
|
__version__ = '4.7.2'
|
||||||
|
|
||||||
|
# Do doctest if we're run directly
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
doctest.testmod()
|
||||||
|
|
||||||
|
__all__ = ["newkeys", "encrypt", "decrypt", "sign", "verify", 'PublicKey',
|
||||||
|
'PrivateKey', 'DecryptionError', 'VerificationError',
|
||||||
|
'find_signature_hash', 'compute_hash', 'sign_hash']
|
||||||
48
rsa/_compat.py
Normal file
48
rsa/_compat.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Python compatibility wrappers."""
|
||||||
|
|
||||||
|
from struct import pack
|
||||||
|
|
||||||
|
|
||||||
|
def byte(num: int) -> bytes:
|
||||||
|
"""
|
||||||
|
Converts a number between 0 and 255 (both inclusive) to a base-256 (byte)
|
||||||
|
representation.
|
||||||
|
|
||||||
|
:param num:
|
||||||
|
An unsigned integer between 0 and 255 (both inclusive).
|
||||||
|
:returns:
|
||||||
|
A single byte.
|
||||||
|
"""
|
||||||
|
return pack("B", num)
|
||||||
|
|
||||||
|
|
||||||
|
def xor_bytes(b1: bytes, b2: bytes) -> bytes:
|
||||||
|
"""
|
||||||
|
Returns the bitwise XOR result between two bytes objects, b1 ^ b2.
|
||||||
|
|
||||||
|
Bitwise XOR operation is commutative, so order of parameters doesn't
|
||||||
|
generate different results. If parameters have different length, extra
|
||||||
|
length of the largest one is ignored.
|
||||||
|
|
||||||
|
:param b1:
|
||||||
|
First bytes object.
|
||||||
|
:param b2:
|
||||||
|
Second bytes object.
|
||||||
|
:returns:
|
||||||
|
Bytes object, result of XOR operation.
|
||||||
|
"""
|
||||||
|
return bytes(x ^ y for x, y in zip(b1, b2))
|
||||||
51
rsa/asn1.py
Normal file
51
rsa/asn1.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""ASN.1 definitions.
|
||||||
|
|
||||||
|
Not all ASN.1-handling code use these definitions, but when it does, they should be here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pyasn1.type import univ, namedtype, tag
|
||||||
|
|
||||||
|
|
||||||
|
class PubKeyHeader(univ.Sequence):
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('oid', univ.ObjectIdentifier()),
|
||||||
|
namedtype.NamedType('parameters', univ.Null()),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenSSLPubKey(univ.Sequence):
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('header', PubKeyHeader()),
|
||||||
|
|
||||||
|
# This little hack (the implicit tag) allows us to get a Bit String as Octet String
|
||||||
|
namedtype.NamedType('key', univ.OctetString().subtype(
|
||||||
|
implicitTag=tag.Tag(tagClass=0, tagFormat=0, tagId=3))),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AsnPubKey(univ.Sequence):
|
||||||
|
"""ASN.1 contents of DER encoded public key:
|
||||||
|
|
||||||
|
RSAPublicKey ::= SEQUENCE {
|
||||||
|
modulus INTEGER, -- n
|
||||||
|
publicExponent INTEGER, -- e
|
||||||
|
"""
|
||||||
|
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('modulus', univ.Integer()),
|
||||||
|
namedtype.NamedType('publicExponent', univ.Integer()),
|
||||||
|
)
|
||||||
292
rsa/cli.py
Normal file
292
rsa/cli.py
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Commandline scripts.
|
||||||
|
|
||||||
|
These scripts are called by the executables defined in setup.py.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import abc
|
||||||
|
import sys
|
||||||
|
import typing
|
||||||
|
import optparse
|
||||||
|
|
||||||
|
import rsa
|
||||||
|
import rsa.key
|
||||||
|
import rsa.pkcs1
|
||||||
|
|
||||||
|
HASH_METHODS = sorted(rsa.pkcs1.HASH_METHODS.keys())
|
||||||
|
Indexable = typing.Union[typing.Tuple, typing.List[str]]
|
||||||
|
|
||||||
|
|
||||||
|
def keygen() -> None:
|
||||||
|
"""Key generator."""
|
||||||
|
|
||||||
|
# Parse the CLI options
|
||||||
|
parser = optparse.OptionParser(usage='usage: %prog [options] keysize',
|
||||||
|
description='Generates a new RSA keypair of "keysize" bits.')
|
||||||
|
|
||||||
|
parser.add_option('--pubout', type='string',
|
||||||
|
help='Output filename for the public key. The public key is '
|
||||||
|
'not saved if this option is not present. You can use '
|
||||||
|
'pyrsa-priv2pub to create the public key file later.')
|
||||||
|
|
||||||
|
parser.add_option('-o', '--out', type='string',
|
||||||
|
help='Output filename for the private key. The key is '
|
||||||
|
'written to stdout if this option is not present.')
|
||||||
|
|
||||||
|
parser.add_option('--form',
|
||||||
|
help='key format of the private and public keys - default PEM',
|
||||||
|
choices=('PEM', 'DER'), default='PEM')
|
||||||
|
|
||||||
|
(cli, cli_args) = parser.parse_args(sys.argv[1:])
|
||||||
|
|
||||||
|
if len(cli_args) != 1:
|
||||||
|
parser.print_help()
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
keysize = int(cli_args[0])
|
||||||
|
except ValueError:
|
||||||
|
parser.print_help()
|
||||||
|
print('Not a valid number: %s' % cli_args[0], file=sys.stderr)
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
print('Generating %i-bit key' % keysize, file=sys.stderr)
|
||||||
|
(pub_key, priv_key) = rsa.newkeys(keysize)
|
||||||
|
|
||||||
|
# Save public key
|
||||||
|
if cli.pubout:
|
||||||
|
print('Writing public key to %s' % cli.pubout, file=sys.stderr)
|
||||||
|
data = pub_key.save_pkcs1(format=cli.form)
|
||||||
|
with open(cli.pubout, 'wb') as outfile:
|
||||||
|
outfile.write(data)
|
||||||
|
|
||||||
|
# Save private key
|
||||||
|
data = priv_key.save_pkcs1(format=cli.form)
|
||||||
|
|
||||||
|
if cli.out:
|
||||||
|
print('Writing private key to %s' % cli.out, file=sys.stderr)
|
||||||
|
with open(cli.out, 'wb') as outfile:
|
||||||
|
outfile.write(data)
|
||||||
|
else:
|
||||||
|
print('Writing private key to stdout', file=sys.stderr)
|
||||||
|
sys.stdout.buffer.write(data)
|
||||||
|
|
||||||
|
|
||||||
|
class CryptoOperation(metaclass=abc.ABCMeta):
|
||||||
|
"""CLI callable that operates with input, output, and a key."""
|
||||||
|
|
||||||
|
keyname = 'public' # or 'private'
|
||||||
|
usage = 'usage: %%prog [options] %(keyname)s_key'
|
||||||
|
description = ''
|
||||||
|
operation = 'decrypt'
|
||||||
|
operation_past = 'decrypted'
|
||||||
|
operation_progressive = 'decrypting'
|
||||||
|
input_help = 'Name of the file to %(operation)s. Reads from stdin if ' \
|
||||||
|
'not specified.'
|
||||||
|
output_help = 'Name of the file to write the %(operation_past)s file ' \
|
||||||
|
'to. Written to stdout if this option is not present.'
|
||||||
|
expected_cli_args = 1
|
||||||
|
has_output = True
|
||||||
|
|
||||||
|
key_class = rsa.PublicKey # type: typing.Type[rsa.key.AbstractKey]
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.usage = self.usage % self.__class__.__dict__
|
||||||
|
self.input_help = self.input_help % self.__class__.__dict__
|
||||||
|
self.output_help = self.output_help % self.__class__.__dict__
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def perform_operation(self, indata: bytes, key: rsa.key.AbstractKey,
|
||||||
|
cli_args: Indexable) -> typing.Any:
|
||||||
|
"""Performs the program's operation.
|
||||||
|
|
||||||
|
Implement in a subclass.
|
||||||
|
|
||||||
|
:returns: the data to write to the output.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __call__(self) -> None:
|
||||||
|
"""Runs the program."""
|
||||||
|
|
||||||
|
(cli, cli_args) = self.parse_cli()
|
||||||
|
|
||||||
|
key = self.read_key(cli_args[0], cli.keyform)
|
||||||
|
|
||||||
|
indata = self.read_infile(cli.input)
|
||||||
|
|
||||||
|
print(self.operation_progressive.title(), file=sys.stderr)
|
||||||
|
outdata = self.perform_operation(indata, key, cli_args)
|
||||||
|
|
||||||
|
if self.has_output:
|
||||||
|
self.write_outfile(outdata, cli.output)
|
||||||
|
|
||||||
|
def parse_cli(self) -> typing.Tuple[optparse.Values, typing.List[str]]:
|
||||||
|
"""Parse the CLI options
|
||||||
|
|
||||||
|
:returns: (cli_opts, cli_args)
|
||||||
|
"""
|
||||||
|
|
||||||
|
parser = optparse.OptionParser(usage=self.usage, description=self.description)
|
||||||
|
|
||||||
|
parser.add_option('-i', '--input', type='string', help=self.input_help)
|
||||||
|
|
||||||
|
if self.has_output:
|
||||||
|
parser.add_option('-o', '--output', type='string', help=self.output_help)
|
||||||
|
|
||||||
|
parser.add_option('--keyform',
|
||||||
|
help='Key format of the %s key - default PEM' % self.keyname,
|
||||||
|
choices=('PEM', 'DER'), default='PEM')
|
||||||
|
|
||||||
|
(cli, cli_args) = parser.parse_args(sys.argv[1:])
|
||||||
|
|
||||||
|
if len(cli_args) != self.expected_cli_args:
|
||||||
|
parser.print_help()
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
return cli, cli_args
|
||||||
|
|
||||||
|
def read_key(self, filename: str, keyform: str) -> rsa.key.AbstractKey:
|
||||||
|
"""Reads a public or private key."""
|
||||||
|
|
||||||
|
print('Reading %s key from %s' % (self.keyname, filename), file=sys.stderr)
|
||||||
|
with open(filename, 'rb') as keyfile:
|
||||||
|
keydata = keyfile.read()
|
||||||
|
|
||||||
|
return self.key_class.load_pkcs1(keydata, keyform)
|
||||||
|
|
||||||
|
def read_infile(self, inname: str) -> bytes:
|
||||||
|
"""Read the input file"""
|
||||||
|
|
||||||
|
if inname:
|
||||||
|
print('Reading input from %s' % inname, file=sys.stderr)
|
||||||
|
with open(inname, 'rb') as infile:
|
||||||
|
return infile.read()
|
||||||
|
|
||||||
|
print('Reading input from stdin', file=sys.stderr)
|
||||||
|
return sys.stdin.buffer.read()
|
||||||
|
|
||||||
|
def write_outfile(self, outdata: bytes, outname: str) -> None:
|
||||||
|
"""Write the output file"""
|
||||||
|
|
||||||
|
if outname:
|
||||||
|
print('Writing output to %s' % outname, file=sys.stderr)
|
||||||
|
with open(outname, 'wb') as outfile:
|
||||||
|
outfile.write(outdata)
|
||||||
|
else:
|
||||||
|
print('Writing output to stdout', file=sys.stderr)
|
||||||
|
sys.stdout.buffer.write(outdata)
|
||||||
|
|
||||||
|
|
||||||
|
class EncryptOperation(CryptoOperation):
|
||||||
|
"""Encrypts a file."""
|
||||||
|
|
||||||
|
keyname = 'public'
|
||||||
|
description = ('Encrypts a file. The file must be shorter than the key '
|
||||||
|
'length in order to be encrypted.')
|
||||||
|
operation = 'encrypt'
|
||||||
|
operation_past = 'encrypted'
|
||||||
|
operation_progressive = 'encrypting'
|
||||||
|
|
||||||
|
def perform_operation(self, indata: bytes, pub_key: rsa.key.AbstractKey,
|
||||||
|
cli_args: Indexable = ()) -> bytes:
|
||||||
|
"""Encrypts files."""
|
||||||
|
assert isinstance(pub_key, rsa.key.PublicKey)
|
||||||
|
return rsa.encrypt(indata, pub_key)
|
||||||
|
|
||||||
|
|
||||||
|
class DecryptOperation(CryptoOperation):
|
||||||
|
"""Decrypts a file."""
|
||||||
|
|
||||||
|
keyname = 'private'
|
||||||
|
description = ('Decrypts a file. The original file must be shorter than '
|
||||||
|
'the key length in order to have been encrypted.')
|
||||||
|
operation = 'decrypt'
|
||||||
|
operation_past = 'decrypted'
|
||||||
|
operation_progressive = 'decrypting'
|
||||||
|
key_class = rsa.PrivateKey
|
||||||
|
|
||||||
|
def perform_operation(self, indata: bytes, priv_key: rsa.key.AbstractKey,
|
||||||
|
cli_args: Indexable = ()) -> bytes:
|
||||||
|
"""Decrypts files."""
|
||||||
|
assert isinstance(priv_key, rsa.key.PrivateKey)
|
||||||
|
return rsa.decrypt(indata, priv_key)
|
||||||
|
|
||||||
|
|
||||||
|
class SignOperation(CryptoOperation):
|
||||||
|
"""Signs a file."""
|
||||||
|
|
||||||
|
keyname = 'private'
|
||||||
|
usage = 'usage: %%prog [options] private_key hash_method'
|
||||||
|
description = ('Signs a file, outputs the signature. Choose the hash '
|
||||||
|
'method from %s' % ', '.join(HASH_METHODS))
|
||||||
|
operation = 'sign'
|
||||||
|
operation_past = 'signature'
|
||||||
|
operation_progressive = 'Signing'
|
||||||
|
key_class = rsa.PrivateKey
|
||||||
|
expected_cli_args = 2
|
||||||
|
|
||||||
|
output_help = ('Name of the file to write the signature to. Written '
|
||||||
|
'to stdout if this option is not present.')
|
||||||
|
|
||||||
|
def perform_operation(self, indata: bytes, priv_key: rsa.key.AbstractKey,
|
||||||
|
cli_args: Indexable) -> bytes:
|
||||||
|
"""Signs files."""
|
||||||
|
assert isinstance(priv_key, rsa.key.PrivateKey)
|
||||||
|
|
||||||
|
hash_method = cli_args[1]
|
||||||
|
if hash_method not in HASH_METHODS:
|
||||||
|
raise SystemExit('Invalid hash method, choose one of %s' %
|
||||||
|
', '.join(HASH_METHODS))
|
||||||
|
|
||||||
|
return rsa.sign(indata, priv_key, hash_method)
|
||||||
|
|
||||||
|
|
||||||
|
class VerifyOperation(CryptoOperation):
|
||||||
|
"""Verify a signature."""
|
||||||
|
|
||||||
|
keyname = 'public'
|
||||||
|
usage = 'usage: %%prog [options] public_key signature_file'
|
||||||
|
description = ('Verifies a signature, exits with status 0 upon success, '
|
||||||
|
'prints an error message and exits with status 1 upon error.')
|
||||||
|
operation = 'verify'
|
||||||
|
operation_past = 'verified'
|
||||||
|
operation_progressive = 'Verifying'
|
||||||
|
key_class = rsa.PublicKey
|
||||||
|
expected_cli_args = 2
|
||||||
|
has_output = False
|
||||||
|
|
||||||
|
def perform_operation(self, indata: bytes, pub_key: rsa.key.AbstractKey,
|
||||||
|
cli_args: Indexable) -> None:
|
||||||
|
"""Verifies files."""
|
||||||
|
assert isinstance(pub_key, rsa.key.PublicKey)
|
||||||
|
|
||||||
|
signature_file = cli_args[1]
|
||||||
|
|
||||||
|
with open(signature_file, 'rb') as sigfile:
|
||||||
|
signature = sigfile.read()
|
||||||
|
|
||||||
|
try:
|
||||||
|
rsa.verify(indata, signature, pub_key)
|
||||||
|
except rsa.VerificationError:
|
||||||
|
raise SystemExit('Verification failed.')
|
||||||
|
|
||||||
|
print('Verification OK', file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
encrypt = EncryptOperation()
|
||||||
|
decrypt = DecryptOperation()
|
||||||
|
sign = SignOperation()
|
||||||
|
verify = VerifyOperation()
|
||||||
185
rsa/common.py
Normal file
185
rsa/common.py
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Common functionality shared by several modules."""
|
||||||
|
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
|
class NotRelativePrimeError(ValueError):
|
||||||
|
def __init__(self, a: int, b: int, d: int, msg: str = '') -> None:
|
||||||
|
super().__init__(msg or "%d and %d are not relatively prime, divider=%i" % (a, b, d))
|
||||||
|
self.a = a
|
||||||
|
self.b = b
|
||||||
|
self.d = d
|
||||||
|
|
||||||
|
|
||||||
|
def bit_size(num: int) -> int:
|
||||||
|
"""
|
||||||
|
Number of bits needed to represent a integer excluding any prefix
|
||||||
|
0 bits.
|
||||||
|
|
||||||
|
Usage::
|
||||||
|
|
||||||
|
>>> bit_size(1023)
|
||||||
|
10
|
||||||
|
>>> bit_size(1024)
|
||||||
|
11
|
||||||
|
>>> bit_size(1025)
|
||||||
|
11
|
||||||
|
|
||||||
|
:param num:
|
||||||
|
Integer value. If num is 0, returns 0. Only the absolute value of the
|
||||||
|
number is considered. Therefore, signed integers will be abs(num)
|
||||||
|
before the number's bit length is determined.
|
||||||
|
:returns:
|
||||||
|
Returns the number of bits in the integer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
return num.bit_length()
|
||||||
|
except AttributeError as ex:
|
||||||
|
raise TypeError('bit_size(num) only supports integers, not %r' % type(num)) from ex
|
||||||
|
|
||||||
|
|
||||||
|
def byte_size(number: int) -> int:
|
||||||
|
"""
|
||||||
|
Returns the number of bytes required to hold a specific long number.
|
||||||
|
|
||||||
|
The number of bytes is rounded up.
|
||||||
|
|
||||||
|
Usage::
|
||||||
|
|
||||||
|
>>> byte_size(1 << 1023)
|
||||||
|
128
|
||||||
|
>>> byte_size((1 << 1024) - 1)
|
||||||
|
128
|
||||||
|
>>> byte_size(1 << 1024)
|
||||||
|
129
|
||||||
|
|
||||||
|
:param number:
|
||||||
|
An unsigned integer
|
||||||
|
:returns:
|
||||||
|
The number of bytes required to hold a specific long number.
|
||||||
|
"""
|
||||||
|
if number == 0:
|
||||||
|
return 1
|
||||||
|
return ceil_div(bit_size(number), 8)
|
||||||
|
|
||||||
|
|
||||||
|
def ceil_div(num: int, div: int) -> int:
|
||||||
|
"""
|
||||||
|
Returns the ceiling function of a division between `num` and `div`.
|
||||||
|
|
||||||
|
Usage::
|
||||||
|
|
||||||
|
>>> ceil_div(100, 7)
|
||||||
|
15
|
||||||
|
>>> ceil_div(100, 10)
|
||||||
|
10
|
||||||
|
>>> ceil_div(1, 4)
|
||||||
|
1
|
||||||
|
|
||||||
|
:param num: Division's numerator, a number
|
||||||
|
:param div: Division's divisor, a number
|
||||||
|
|
||||||
|
:return: Rounded up result of the division between the parameters.
|
||||||
|
"""
|
||||||
|
quanta, mod = divmod(num, div)
|
||||||
|
if mod:
|
||||||
|
quanta += 1
|
||||||
|
return quanta
|
||||||
|
|
||||||
|
|
||||||
|
def extended_gcd(a: int, b: int) -> typing.Tuple[int, int, int]:
|
||||||
|
"""Returns a tuple (r, i, j) such that r = gcd(a, b) = ia + jb
|
||||||
|
"""
|
||||||
|
# r = gcd(a,b) i = multiplicitive inverse of a mod b
|
||||||
|
# or j = multiplicitive inverse of b mod a
|
||||||
|
# Neg return values for i or j are made positive mod b or a respectively
|
||||||
|
# Iterateive Version is faster and uses much less stack space
|
||||||
|
x = 0
|
||||||
|
y = 1
|
||||||
|
lx = 1
|
||||||
|
ly = 0
|
||||||
|
oa = a # Remember original a/b to remove
|
||||||
|
ob = b # negative values from return results
|
||||||
|
while b != 0:
|
||||||
|
q = a // b
|
||||||
|
(a, b) = (b, a % b)
|
||||||
|
(x, lx) = ((lx - (q * x)), x)
|
||||||
|
(y, ly) = ((ly - (q * y)), y)
|
||||||
|
if lx < 0:
|
||||||
|
lx += ob # If neg wrap modulo orignal b
|
||||||
|
if ly < 0:
|
||||||
|
ly += oa # If neg wrap modulo orignal a
|
||||||
|
return a, lx, ly # Return only positive values
|
||||||
|
|
||||||
|
|
||||||
|
def inverse(x: int, n: int) -> int:
|
||||||
|
"""Returns the inverse of x % n under multiplication, a.k.a x^-1 (mod n)
|
||||||
|
|
||||||
|
>>> inverse(7, 4)
|
||||||
|
3
|
||||||
|
>>> (inverse(143, 4) * 143) % 4
|
||||||
|
1
|
||||||
|
"""
|
||||||
|
|
||||||
|
(divider, inv, _) = extended_gcd(x, n)
|
||||||
|
|
||||||
|
if divider != 1:
|
||||||
|
raise NotRelativePrimeError(x, n, divider)
|
||||||
|
|
||||||
|
return inv
|
||||||
|
|
||||||
|
|
||||||
|
def crt(a_values: typing.Iterable[int], modulo_values: typing.Iterable[int]) -> int:
|
||||||
|
"""Chinese Remainder Theorem.
|
||||||
|
|
||||||
|
Calculates x such that x = a[i] (mod m[i]) for each i.
|
||||||
|
|
||||||
|
:param a_values: the a-values of the above equation
|
||||||
|
:param modulo_values: the m-values of the above equation
|
||||||
|
:returns: x such that x = a[i] (mod m[i]) for each i
|
||||||
|
|
||||||
|
|
||||||
|
>>> crt([2, 3], [3, 5])
|
||||||
|
8
|
||||||
|
|
||||||
|
>>> crt([2, 3, 2], [3, 5, 7])
|
||||||
|
23
|
||||||
|
|
||||||
|
>>> crt([2, 3, 0], [7, 11, 15])
|
||||||
|
135
|
||||||
|
"""
|
||||||
|
|
||||||
|
m = 1
|
||||||
|
x = 0
|
||||||
|
|
||||||
|
for modulo in modulo_values:
|
||||||
|
m *= modulo
|
||||||
|
|
||||||
|
for (m_i, a_i) in zip(modulo_values, a_values):
|
||||||
|
M_i = m // m_i
|
||||||
|
inv = inverse(M_i, m_i)
|
||||||
|
|
||||||
|
x = (x + a_i * M_i * inv) % m
|
||||||
|
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
doctest.testmod()
|
||||||
53
rsa/core.py
Normal file
53
rsa/core.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Core mathematical operations.
|
||||||
|
|
||||||
|
This is the actual core RSA implementation, which is only defined
|
||||||
|
mathematically on integers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def assert_int(var: int, name: str) -> None:
|
||||||
|
if isinstance(var, int):
|
||||||
|
return
|
||||||
|
|
||||||
|
raise TypeError('%s should be an integer, not %s' % (name, var.__class__))
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt_int(message: int, ekey: int, n: int) -> int:
|
||||||
|
"""Encrypts a message using encryption key 'ekey', working modulo n"""
|
||||||
|
|
||||||
|
assert_int(message, 'message')
|
||||||
|
assert_int(ekey, 'ekey')
|
||||||
|
assert_int(n, 'n')
|
||||||
|
|
||||||
|
if message < 0:
|
||||||
|
raise ValueError('Only non-negative numbers are supported')
|
||||||
|
|
||||||
|
if message > n:
|
||||||
|
raise OverflowError("The message %i is too long for n=%i" % (message, n))
|
||||||
|
|
||||||
|
return pow(message, ekey, n)
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt_int(cyphertext: int, dkey: int, n: int) -> int:
|
||||||
|
"""Decrypts a cypher text using the decryption key 'dkey', working modulo n"""
|
||||||
|
|
||||||
|
assert_int(cyphertext, 'cyphertext')
|
||||||
|
assert_int(dkey, 'dkey')
|
||||||
|
assert_int(n, 'n')
|
||||||
|
|
||||||
|
message = pow(cyphertext, dkey, n)
|
||||||
|
return message
|
||||||
831
rsa/key.py
Normal file
831
rsa/key.py
Normal file
@ -0,0 +1,831 @@
|
|||||||
|
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""RSA key generation code.
|
||||||
|
|
||||||
|
Create new keys with the newkeys() function. It will give you a PublicKey and a
|
||||||
|
PrivateKey object.
|
||||||
|
|
||||||
|
Loading and saving keys requires the pyasn1 module. This module is imported as
|
||||||
|
late as possible, such that other functionality will remain working in absence
|
||||||
|
of pyasn1.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Storing public and private keys via the `pickle` module is possible.
|
||||||
|
However, it is insecure to load a key from an untrusted source.
|
||||||
|
The pickle module is not secure against erroneous or maliciously
|
||||||
|
constructed data. Never unpickle data received from an untrusted
|
||||||
|
or unauthenticated source.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
import typing
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import rsa.prime
|
||||||
|
import rsa.pem
|
||||||
|
import rsa.common
|
||||||
|
import rsa.randnum
|
||||||
|
import rsa.core
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
DEFAULT_EXPONENT = 65537
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractKey:
|
||||||
|
"""Abstract superclass for private and public keys."""
|
||||||
|
|
||||||
|
__slots__ = ('n', 'e', 'blindfac', 'blindfac_inverse', 'mutex')
|
||||||
|
|
||||||
|
def __init__(self, n: int, e: int) -> None:
|
||||||
|
self.n = n
|
||||||
|
self.e = e
|
||||||
|
|
||||||
|
# These will be computed properly on the first call to blind().
|
||||||
|
self.blindfac = self.blindfac_inverse = -1
|
||||||
|
|
||||||
|
# Used to protect updates to the blinding factor in multi-threaded
|
||||||
|
# environments.
|
||||||
|
self.mutex = threading.Lock()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _load_pkcs1_pem(cls, keyfile: bytes) -> 'AbstractKey':
|
||||||
|
"""Loads a key in PKCS#1 PEM format, implement in a subclass.
|
||||||
|
|
||||||
|
:param keyfile: contents of a PEM-encoded file that contains
|
||||||
|
the public key.
|
||||||
|
:type keyfile: bytes
|
||||||
|
|
||||||
|
:return: the loaded key
|
||||||
|
:rtype: AbstractKey
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _load_pkcs1_der(cls, keyfile: bytes) -> 'AbstractKey':
|
||||||
|
"""Loads a key in PKCS#1 PEM format, implement in a subclass.
|
||||||
|
|
||||||
|
:param keyfile: contents of a DER-encoded file that contains
|
||||||
|
the public key.
|
||||||
|
:type keyfile: bytes
|
||||||
|
|
||||||
|
:return: the loaded key
|
||||||
|
:rtype: AbstractKey
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _save_pkcs1_pem(self) -> bytes:
|
||||||
|
"""Saves the key in PKCS#1 PEM format, implement in a subclass.
|
||||||
|
|
||||||
|
:returns: the PEM-encoded key.
|
||||||
|
:rtype: bytes
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _save_pkcs1_der(self) -> bytes:
|
||||||
|
"""Saves the key in PKCS#1 DER format, implement in a subclass.
|
||||||
|
|
||||||
|
:returns: the DER-encoded key.
|
||||||
|
:rtype: bytes
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_pkcs1(cls, keyfile: bytes, format: str = 'PEM') -> 'AbstractKey':
|
||||||
|
"""Loads a key in PKCS#1 DER or PEM format.
|
||||||
|
|
||||||
|
:param keyfile: contents of a DER- or PEM-encoded file that contains
|
||||||
|
the key.
|
||||||
|
:type keyfile: bytes
|
||||||
|
:param format: the format of the file to load; 'PEM' or 'DER'
|
||||||
|
:type format: str
|
||||||
|
|
||||||
|
:return: the loaded key
|
||||||
|
:rtype: AbstractKey
|
||||||
|
"""
|
||||||
|
|
||||||
|
methods = {
|
||||||
|
'PEM': cls._load_pkcs1_pem,
|
||||||
|
'DER': cls._load_pkcs1_der,
|
||||||
|
}
|
||||||
|
|
||||||
|
method = cls._assert_format_exists(format, methods)
|
||||||
|
return method(keyfile)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _assert_format_exists(file_format: str, methods: typing.Mapping[str, typing.Callable]) \
|
||||||
|
-> typing.Callable:
|
||||||
|
"""Checks whether the given file format exists in 'methods'.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
return methods[file_format]
|
||||||
|
except KeyError:
|
||||||
|
formats = ', '.join(sorted(methods.keys()))
|
||||||
|
raise ValueError('Unsupported format: %r, try one of %s' % (file_format,
|
||||||
|
formats))
|
||||||
|
|
||||||
|
def save_pkcs1(self, format: str = 'PEM') -> bytes:
|
||||||
|
"""Saves the key in PKCS#1 DER or PEM format.
|
||||||
|
|
||||||
|
:param format: the format to save; 'PEM' or 'DER'
|
||||||
|
:type format: str
|
||||||
|
:returns: the DER- or PEM-encoded key.
|
||||||
|
:rtype: bytes
|
||||||
|
"""
|
||||||
|
|
||||||
|
methods = {
|
||||||
|
'PEM': self._save_pkcs1_pem,
|
||||||
|
'DER': self._save_pkcs1_der,
|
||||||
|
}
|
||||||
|
|
||||||
|
method = self._assert_format_exists(format, methods)
|
||||||
|
return method()
|
||||||
|
|
||||||
|
def blind(self, message: int) -> typing.Tuple[int, int]:
|
||||||
|
"""Performs blinding on the message.
|
||||||
|
|
||||||
|
:param message: the message, as integer, to blind.
|
||||||
|
:param r: the random number to blind with.
|
||||||
|
:return: tuple (the blinded message, the inverse of the used blinding factor)
|
||||||
|
|
||||||
|
The blinding is such that message = unblind(decrypt(blind(encrypt(message))).
|
||||||
|
|
||||||
|
See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
|
||||||
|
"""
|
||||||
|
blindfac, blindfac_inverse = self._update_blinding_factor()
|
||||||
|
blinded = (message * pow(blindfac, self.e, self.n)) % self.n
|
||||||
|
return blinded, blindfac_inverse
|
||||||
|
|
||||||
|
def unblind(self, blinded: int, blindfac_inverse: int) -> int:
|
||||||
|
"""Performs blinding on the message using random number 'blindfac_inverse'.
|
||||||
|
|
||||||
|
:param blinded: the blinded message, as integer, to unblind.
|
||||||
|
:param blindfac: the factor to unblind with.
|
||||||
|
:return: the original message.
|
||||||
|
|
||||||
|
The blinding is such that message = unblind(decrypt(blind(encrypt(message))).
|
||||||
|
|
||||||
|
See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
|
||||||
|
"""
|
||||||
|
return (blindfac_inverse * blinded) % self.n
|
||||||
|
|
||||||
|
def _initial_blinding_factor(self) -> int:
|
||||||
|
for _ in range(1000):
|
||||||
|
blind_r = rsa.randnum.randint(self.n - 1)
|
||||||
|
if rsa.prime.are_relatively_prime(self.n, blind_r):
|
||||||
|
return blind_r
|
||||||
|
raise RuntimeError('unable to find blinding factor')
|
||||||
|
|
||||||
|
def _update_blinding_factor(self) -> typing.Tuple[int, int]:
|
||||||
|
"""Update blinding factors.
|
||||||
|
|
||||||
|
Computing a blinding factor is expensive, so instead this function
|
||||||
|
does this once, then updates the blinding factor as per section 9
|
||||||
|
of 'A Timing Attack against RSA with the Chinese Remainder Theorem'
|
||||||
|
by Werner Schindler.
|
||||||
|
See https://tls.mbed.org/public/WSchindler-RSA_Timing_Attack.pdf
|
||||||
|
|
||||||
|
:return: the new blinding factor and its inverse.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with self.mutex:
|
||||||
|
if self.blindfac < 0:
|
||||||
|
# Compute initial blinding factor, which is rather slow to do.
|
||||||
|
self.blindfac = self._initial_blinding_factor()
|
||||||
|
self.blindfac_inverse = rsa.common.inverse(self.blindfac, self.n)
|
||||||
|
else:
|
||||||
|
# Reuse previous blinding factor.
|
||||||
|
self.blindfac = pow(self.blindfac, 2, self.n)
|
||||||
|
self.blindfac_inverse = pow(self.blindfac_inverse, 2, self.n)
|
||||||
|
|
||||||
|
return self.blindfac, self.blindfac_inverse
|
||||||
|
|
||||||
|
class PublicKey(AbstractKey):
|
||||||
|
"""Represents a public RSA key.
|
||||||
|
|
||||||
|
This key is also known as the 'encryption key'. It contains the 'n' and 'e'
|
||||||
|
values.
|
||||||
|
|
||||||
|
Supports attributes as well as dictionary-like access. Attribute access is
|
||||||
|
faster, though.
|
||||||
|
|
||||||
|
>>> PublicKey(5, 3)
|
||||||
|
PublicKey(5, 3)
|
||||||
|
|
||||||
|
>>> key = PublicKey(5, 3)
|
||||||
|
>>> key.n
|
||||||
|
5
|
||||||
|
>>> key['n']
|
||||||
|
5
|
||||||
|
>>> key.e
|
||||||
|
3
|
||||||
|
>>> key['e']
|
||||||
|
3
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('n', 'e')
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> int:
|
||||||
|
return getattr(self, key)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return 'PublicKey(%i, %i)' % (self.n, self.e)
|
||||||
|
|
||||||
|
def __getstate__(self) -> typing.Tuple[int, int]:
|
||||||
|
"""Returns the key as tuple for pickling."""
|
||||||
|
return self.n, self.e
|
||||||
|
|
||||||
|
def __setstate__(self, state: typing.Tuple[int, int]) -> None:
|
||||||
|
"""Sets the key from tuple."""
|
||||||
|
self.n, self.e = state
|
||||||
|
AbstractKey.__init__(self, self.n, self.e)
|
||||||
|
|
||||||
|
def __eq__(self, other: typing.Any) -> bool:
|
||||||
|
if other is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not isinstance(other, PublicKey):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.n == other.n and self.e == other.e
|
||||||
|
|
||||||
|
def __ne__(self, other: typing.Any) -> bool:
|
||||||
|
return not (self == other)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash((self.n, self.e))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _load_pkcs1_der(cls, keyfile: bytes) -> 'PublicKey':
|
||||||
|
"""Loads a key in PKCS#1 DER format.
|
||||||
|
|
||||||
|
:param keyfile: contents of a DER-encoded file that contains the public
|
||||||
|
key.
|
||||||
|
:return: a PublicKey object
|
||||||
|
|
||||||
|
First let's construct a DER encoded key:
|
||||||
|
|
||||||
|
>>> import base64
|
||||||
|
>>> b64der = 'MAwCBQCNGmYtAgMBAAE='
|
||||||
|
>>> der = base64.standard_b64decode(b64der)
|
||||||
|
|
||||||
|
This loads the file:
|
||||||
|
|
||||||
|
>>> PublicKey._load_pkcs1_der(der)
|
||||||
|
PublicKey(2367317549, 65537)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pyasn1.codec.der import decoder
|
||||||
|
from rsa.asn1 import AsnPubKey
|
||||||
|
|
||||||
|
(priv, _) = decoder.decode(keyfile, asn1Spec=AsnPubKey())
|
||||||
|
return cls(n=int(priv['modulus']), e=int(priv['publicExponent']))
|
||||||
|
|
||||||
|
def _save_pkcs1_der(self) -> bytes:
|
||||||
|
"""Saves the public key in PKCS#1 DER format.
|
||||||
|
|
||||||
|
:returns: the DER-encoded public key.
|
||||||
|
:rtype: bytes
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pyasn1.codec.der import encoder
|
||||||
|
from rsa.asn1 import AsnPubKey
|
||||||
|
|
||||||
|
# Create the ASN object
|
||||||
|
asn_key = AsnPubKey()
|
||||||
|
asn_key.setComponentByName('modulus', self.n)
|
||||||
|
asn_key.setComponentByName('publicExponent', self.e)
|
||||||
|
|
||||||
|
return encoder.encode(asn_key)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _load_pkcs1_pem(cls, keyfile: bytes) -> 'PublicKey':
|
||||||
|
"""Loads a PKCS#1 PEM-encoded public key file.
|
||||||
|
|
||||||
|
The contents of the file before the "-----BEGIN RSA PUBLIC KEY-----" and
|
||||||
|
after the "-----END RSA PUBLIC KEY-----" lines is ignored.
|
||||||
|
|
||||||
|
:param keyfile: contents of a PEM-encoded file that contains the public
|
||||||
|
key.
|
||||||
|
:return: a PublicKey object
|
||||||
|
"""
|
||||||
|
|
||||||
|
der = rsa.pem.load_pem(keyfile, 'RSA PUBLIC KEY')
|
||||||
|
return cls._load_pkcs1_der(der)
|
||||||
|
|
||||||
|
def _save_pkcs1_pem(self) -> bytes:
|
||||||
|
"""Saves a PKCS#1 PEM-encoded public key file.
|
||||||
|
|
||||||
|
:return: contents of a PEM-encoded file that contains the public key.
|
||||||
|
:rtype: bytes
|
||||||
|
"""
|
||||||
|
|
||||||
|
der = self._save_pkcs1_der()
|
||||||
|
return rsa.pem.save_pem(der, 'RSA PUBLIC KEY')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_pkcs1_openssl_pem(cls, keyfile: bytes) -> 'PublicKey':
|
||||||
|
"""Loads a PKCS#1.5 PEM-encoded public key file from OpenSSL.
|
||||||
|
|
||||||
|
These files can be recognised in that they start with BEGIN PUBLIC KEY
|
||||||
|
rather than BEGIN RSA PUBLIC KEY.
|
||||||
|
|
||||||
|
The contents of the file before the "-----BEGIN PUBLIC KEY-----" and
|
||||||
|
after the "-----END PUBLIC KEY-----" lines is ignored.
|
||||||
|
|
||||||
|
:param keyfile: contents of a PEM-encoded file that contains the public
|
||||||
|
key, from OpenSSL.
|
||||||
|
:type keyfile: bytes
|
||||||
|
:return: a PublicKey object
|
||||||
|
"""
|
||||||
|
|
||||||
|
der = rsa.pem.load_pem(keyfile, 'PUBLIC KEY')
|
||||||
|
return cls.load_pkcs1_openssl_der(der)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_pkcs1_openssl_der(cls, keyfile: bytes) -> 'PublicKey':
|
||||||
|
"""Loads a PKCS#1 DER-encoded public key file from OpenSSL.
|
||||||
|
|
||||||
|
:param keyfile: contents of a DER-encoded file that contains the public
|
||||||
|
key, from OpenSSL.
|
||||||
|
:return: a PublicKey object
|
||||||
|
"""
|
||||||
|
|
||||||
|
from rsa.asn1 import OpenSSLPubKey
|
||||||
|
from pyasn1.codec.der import decoder
|
||||||
|
from pyasn1.type import univ
|
||||||
|
|
||||||
|
(keyinfo, _) = decoder.decode(keyfile, asn1Spec=OpenSSLPubKey())
|
||||||
|
|
||||||
|
if keyinfo['header']['oid'] != univ.ObjectIdentifier('1.2.840.113549.1.1.1'):
|
||||||
|
raise TypeError("This is not a DER-encoded OpenSSL-compatible public key")
|
||||||
|
|
||||||
|
return cls._load_pkcs1_der(keyinfo['key'][1:])
|
||||||
|
|
||||||
|
|
||||||
|
class PrivateKey(AbstractKey):
|
||||||
|
"""Represents a private RSA key.
|
||||||
|
|
||||||
|
This key is also known as the 'decryption key'. It contains the 'n', 'e',
|
||||||
|
'd', 'p', 'q' and other values.
|
||||||
|
|
||||||
|
Supports attributes as well as dictionary-like access. Attribute access is
|
||||||
|
faster, though.
|
||||||
|
|
||||||
|
>>> PrivateKey(3247, 65537, 833, 191, 17)
|
||||||
|
PrivateKey(3247, 65537, 833, 191, 17)
|
||||||
|
|
||||||
|
exp1, exp2 and coef will be calculated:
|
||||||
|
|
||||||
|
>>> pk = PrivateKey(3727264081, 65537, 3349121513, 65063, 57287)
|
||||||
|
>>> pk.exp1
|
||||||
|
55063
|
||||||
|
>>> pk.exp2
|
||||||
|
10095
|
||||||
|
>>> pk.coef
|
||||||
|
50797
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('n', 'e', 'd', 'p', 'q', 'exp1', 'exp2', 'coef')
|
||||||
|
|
||||||
|
def __init__(self, n: int, e: int, d: int, p: int, q: int) -> None:
|
||||||
|
AbstractKey.__init__(self, n, e)
|
||||||
|
self.d = d
|
||||||
|
self.p = p
|
||||||
|
self.q = q
|
||||||
|
|
||||||
|
# Calculate exponents and coefficient.
|
||||||
|
self.exp1 = int(d % (p - 1))
|
||||||
|
self.exp2 = int(d % (q - 1))
|
||||||
|
self.coef = rsa.common.inverse(q, p)
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> int:
|
||||||
|
return getattr(self, key)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return 'PrivateKey(%i, %i, %i, %i, %i)' % (self.n, self.e, self.d, self.p, self.q)
|
||||||
|
|
||||||
|
def __getstate__(self) -> typing.Tuple[int, int, int, int, int, int, int, int]:
|
||||||
|
"""Returns the key as tuple for pickling."""
|
||||||
|
return self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef
|
||||||
|
|
||||||
|
def __setstate__(self, state: typing.Tuple[int, int, int, int, int, int, int, int]) -> None:
|
||||||
|
"""Sets the key from tuple."""
|
||||||
|
self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef = state
|
||||||
|
AbstractKey.__init__(self, self.n, self.e)
|
||||||
|
|
||||||
|
def __eq__(self, other: typing.Any) -> bool:
|
||||||
|
if other is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not isinstance(other, PrivateKey):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return (self.n == other.n and
|
||||||
|
self.e == other.e and
|
||||||
|
self.d == other.d and
|
||||||
|
self.p == other.p and
|
||||||
|
self.q == other.q and
|
||||||
|
self.exp1 == other.exp1 and
|
||||||
|
self.exp2 == other.exp2 and
|
||||||
|
self.coef == other.coef)
|
||||||
|
|
||||||
|
def __ne__(self, other: typing.Any) -> bool:
|
||||||
|
return not (self == other)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash((self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef))
|
||||||
|
|
||||||
|
def blinded_decrypt(self, encrypted: int) -> int:
|
||||||
|
"""Decrypts the message using blinding to prevent side-channel attacks.
|
||||||
|
|
||||||
|
:param encrypted: the encrypted message
|
||||||
|
:type encrypted: int
|
||||||
|
|
||||||
|
:returns: the decrypted message
|
||||||
|
:rtype: int
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Blinding and un-blinding should be using the same factor
|
||||||
|
blinded, blindfac_inverse = self.blind(encrypted)
|
||||||
|
decrypted = rsa.core.decrypt_int(blinded, self.d, self.n)
|
||||||
|
return self.unblind(decrypted, blindfac_inverse)
|
||||||
|
|
||||||
|
def blinded_encrypt(self, message: int) -> int:
|
||||||
|
"""Encrypts the message using blinding to prevent side-channel attacks.
|
||||||
|
|
||||||
|
:param message: the message to encrypt
|
||||||
|
:type message: int
|
||||||
|
|
||||||
|
:returns: the encrypted message
|
||||||
|
:rtype: int
|
||||||
|
"""
|
||||||
|
|
||||||
|
blinded, blindfac_inverse = self.blind(message)
|
||||||
|
encrypted = rsa.core.encrypt_int(blinded, self.d, self.n)
|
||||||
|
return self.unblind(encrypted, blindfac_inverse)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _load_pkcs1_der(cls, keyfile: bytes) -> 'PrivateKey':
|
||||||
|
"""Loads a key in PKCS#1 DER format.
|
||||||
|
|
||||||
|
:param keyfile: contents of a DER-encoded file that contains the private
|
||||||
|
key.
|
||||||
|
:type keyfile: bytes
|
||||||
|
:return: a PrivateKey object
|
||||||
|
|
||||||
|
First let's construct a DER encoded key:
|
||||||
|
|
||||||
|
>>> import base64
|
||||||
|
>>> b64der = 'MC4CAQACBQDeKYlRAgMBAAECBQDHn4npAgMA/icCAwDfxwIDANcXAgInbwIDAMZt'
|
||||||
|
>>> der = base64.standard_b64decode(b64der)
|
||||||
|
|
||||||
|
This loads the file:
|
||||||
|
|
||||||
|
>>> PrivateKey._load_pkcs1_der(der)
|
||||||
|
PrivateKey(3727264081, 65537, 3349121513, 65063, 57287)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pyasn1.codec.der import decoder
|
||||||
|
(priv, _) = decoder.decode(keyfile)
|
||||||
|
|
||||||
|
# ASN.1 contents of DER encoded private key:
|
||||||
|
#
|
||||||
|
# RSAPrivateKey ::= SEQUENCE {
|
||||||
|
# version Version,
|
||||||
|
# modulus INTEGER, -- n
|
||||||
|
# publicExponent INTEGER, -- e
|
||||||
|
# privateExponent INTEGER, -- d
|
||||||
|
# prime1 INTEGER, -- p
|
||||||
|
# prime2 INTEGER, -- q
|
||||||
|
# exponent1 INTEGER, -- d mod (p-1)
|
||||||
|
# exponent2 INTEGER, -- d mod (q-1)
|
||||||
|
# coefficient INTEGER, -- (inverse of q) mod p
|
||||||
|
# otherPrimeInfos OtherPrimeInfos OPTIONAL
|
||||||
|
# }
|
||||||
|
|
||||||
|
if priv[0] != 0:
|
||||||
|
raise ValueError('Unable to read this file, version %s != 0' % priv[0])
|
||||||
|
|
||||||
|
as_ints = map(int, priv[1:6])
|
||||||
|
key = cls(*as_ints)
|
||||||
|
|
||||||
|
exp1, exp2, coef = map(int, priv[6:9])
|
||||||
|
|
||||||
|
if (key.exp1, key.exp2, key.coef) != (exp1, exp2, coef):
|
||||||
|
warnings.warn(
|
||||||
|
'You have provided a malformed keyfile. Either the exponents '
|
||||||
|
'or the coefficient are incorrect. Using the correct values '
|
||||||
|
'instead.',
|
||||||
|
UserWarning,
|
||||||
|
)
|
||||||
|
|
||||||
|
return key
|
||||||
|
|
||||||
|
def _save_pkcs1_der(self) -> bytes:
|
||||||
|
"""Saves the private key in PKCS#1 DER format.
|
||||||
|
|
||||||
|
:returns: the DER-encoded private key.
|
||||||
|
:rtype: bytes
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pyasn1.type import univ, namedtype
|
||||||
|
from pyasn1.codec.der import encoder
|
||||||
|
|
||||||
|
class AsnPrivKey(univ.Sequence):
|
||||||
|
componentType = namedtype.NamedTypes(
|
||||||
|
namedtype.NamedType('version', univ.Integer()),
|
||||||
|
namedtype.NamedType('modulus', univ.Integer()),
|
||||||
|
namedtype.NamedType('publicExponent', univ.Integer()),
|
||||||
|
namedtype.NamedType('privateExponent', univ.Integer()),
|
||||||
|
namedtype.NamedType('prime1', univ.Integer()),
|
||||||
|
namedtype.NamedType('prime2', univ.Integer()),
|
||||||
|
namedtype.NamedType('exponent1', univ.Integer()),
|
||||||
|
namedtype.NamedType('exponent2', univ.Integer()),
|
||||||
|
namedtype.NamedType('coefficient', univ.Integer()),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create the ASN object
|
||||||
|
asn_key = AsnPrivKey()
|
||||||
|
asn_key.setComponentByName('version', 0)
|
||||||
|
asn_key.setComponentByName('modulus', self.n)
|
||||||
|
asn_key.setComponentByName('publicExponent', self.e)
|
||||||
|
asn_key.setComponentByName('privateExponent', self.d)
|
||||||
|
asn_key.setComponentByName('prime1', self.p)
|
||||||
|
asn_key.setComponentByName('prime2', self.q)
|
||||||
|
asn_key.setComponentByName('exponent1', self.exp1)
|
||||||
|
asn_key.setComponentByName('exponent2', self.exp2)
|
||||||
|
asn_key.setComponentByName('coefficient', self.coef)
|
||||||
|
|
||||||
|
return encoder.encode(asn_key)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _load_pkcs1_pem(cls, keyfile: bytes) -> 'PrivateKey':
|
||||||
|
"""Loads a PKCS#1 PEM-encoded private key file.
|
||||||
|
|
||||||
|
The contents of the file before the "-----BEGIN RSA PRIVATE KEY-----" and
|
||||||
|
after the "-----END RSA PRIVATE KEY-----" lines is ignored.
|
||||||
|
|
||||||
|
:param keyfile: contents of a PEM-encoded file that contains the private
|
||||||
|
key.
|
||||||
|
:type keyfile: bytes
|
||||||
|
:return: a PrivateKey object
|
||||||
|
"""
|
||||||
|
|
||||||
|
der = rsa.pem.load_pem(keyfile, b'RSA PRIVATE KEY')
|
||||||
|
return cls._load_pkcs1_der(der)
|
||||||
|
|
||||||
|
def _save_pkcs1_pem(self) -> bytes:
|
||||||
|
"""Saves a PKCS#1 PEM-encoded private key file.
|
||||||
|
|
||||||
|
:return: contents of a PEM-encoded file that contains the private key.
|
||||||
|
:rtype: bytes
|
||||||
|
"""
|
||||||
|
|
||||||
|
der = self._save_pkcs1_der()
|
||||||
|
return rsa.pem.save_pem(der, b'RSA PRIVATE KEY')
|
||||||
|
|
||||||
|
|
||||||
|
def find_p_q(nbits: int,
|
||||||
|
getprime_func: typing.Callable[[int], int] = rsa.prime.getprime,
|
||||||
|
accurate: bool = True) -> typing.Tuple[int, int]:
|
||||||
|
"""Returns a tuple of two different primes of nbits bits each.
|
||||||
|
|
||||||
|
The resulting p * q has exacty 2 * nbits bits, and the returned p and q
|
||||||
|
will not be equal.
|
||||||
|
|
||||||
|
:param nbits: the number of bits in each of p and q.
|
||||||
|
:param getprime_func: the getprime function, defaults to
|
||||||
|
:py:func:`rsa.prime.getprime`.
|
||||||
|
|
||||||
|
*Introduced in Python-RSA 3.1*
|
||||||
|
|
||||||
|
:param accurate: whether to enable accurate mode or not.
|
||||||
|
:returns: (p, q), where p > q
|
||||||
|
|
||||||
|
>>> (p, q) = find_p_q(128)
|
||||||
|
>>> from rsa import common
|
||||||
|
>>> common.bit_size(p * q)
|
||||||
|
256
|
||||||
|
|
||||||
|
When not in accurate mode, the number of bits can be slightly less
|
||||||
|
|
||||||
|
>>> (p, q) = find_p_q(128, accurate=False)
|
||||||
|
>>> from rsa import common
|
||||||
|
>>> common.bit_size(p * q) <= 256
|
||||||
|
True
|
||||||
|
>>> common.bit_size(p * q) > 240
|
||||||
|
True
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
total_bits = nbits * 2
|
||||||
|
|
||||||
|
# Make sure that p and q aren't too close or the factoring programs can
|
||||||
|
# factor n.
|
||||||
|
shift = nbits // 16
|
||||||
|
pbits = nbits + shift
|
||||||
|
qbits = nbits - shift
|
||||||
|
|
||||||
|
# Choose the two initial primes
|
||||||
|
log.debug('find_p_q(%i): Finding p', nbits)
|
||||||
|
p = getprime_func(pbits)
|
||||||
|
log.debug('find_p_q(%i): Finding q', nbits)
|
||||||
|
q = getprime_func(qbits)
|
||||||
|
|
||||||
|
def is_acceptable(p: int, q: int) -> bool:
|
||||||
|
"""Returns True iff p and q are acceptable:
|
||||||
|
|
||||||
|
- p and q differ
|
||||||
|
- (p * q) has the right nr of bits (when accurate=True)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if p == q:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not accurate:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Make sure we have just the right amount of bits
|
||||||
|
found_size = rsa.common.bit_size(p * q)
|
||||||
|
return total_bits == found_size
|
||||||
|
|
||||||
|
# Keep choosing other primes until they match our requirements.
|
||||||
|
change_p = False
|
||||||
|
while not is_acceptable(p, q):
|
||||||
|
# Change p on one iteration and q on the other
|
||||||
|
if change_p:
|
||||||
|
p = getprime_func(pbits)
|
||||||
|
else:
|
||||||
|
q = getprime_func(qbits)
|
||||||
|
|
||||||
|
change_p = not change_p
|
||||||
|
|
||||||
|
# We want p > q as described on
|
||||||
|
# http://www.di-mgt.com.au/rsa_alg.html#crt
|
||||||
|
return max(p, q), min(p, q)
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_keys_custom_exponent(p: int, q: int, exponent: int) -> typing.Tuple[int, int]:
|
||||||
|
"""Calculates an encryption and a decryption key given p, q and an exponent,
|
||||||
|
and returns them as a tuple (e, d)
|
||||||
|
|
||||||
|
:param p: the first large prime
|
||||||
|
:param q: the second large prime
|
||||||
|
:param exponent: the exponent for the key; only change this if you know
|
||||||
|
what you're doing, as the exponent influences how difficult your
|
||||||
|
private key can be cracked. A very common choice for e is 65537.
|
||||||
|
:type exponent: int
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
phi_n = (p - 1) * (q - 1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
d = rsa.common.inverse(exponent, phi_n)
|
||||||
|
except rsa.common.NotRelativePrimeError as ex:
|
||||||
|
raise rsa.common.NotRelativePrimeError(
|
||||||
|
exponent, phi_n, ex.d,
|
||||||
|
msg="e (%d) and phi_n (%d) are not relatively prime (divider=%i)" %
|
||||||
|
(exponent, phi_n, ex.d))
|
||||||
|
|
||||||
|
if (exponent * d) % phi_n != 1:
|
||||||
|
raise ValueError("e (%d) and d (%d) are not mult. inv. modulo "
|
||||||
|
"phi_n (%d)" % (exponent, d, phi_n))
|
||||||
|
|
||||||
|
return exponent, d
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_keys(p: int, q: int) -> typing.Tuple[int, int]:
|
||||||
|
"""Calculates an encryption and a decryption key given p and q, and
|
||||||
|
returns them as a tuple (e, d)
|
||||||
|
|
||||||
|
:param p: the first large prime
|
||||||
|
:param q: the second large prime
|
||||||
|
|
||||||
|
:return: tuple (e, d) with the encryption and decryption exponents.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return calculate_keys_custom_exponent(p, q, DEFAULT_EXPONENT)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_keys(nbits: int,
|
||||||
|
getprime_func: typing.Callable[[int], int],
|
||||||
|
accurate: bool = True,
|
||||||
|
exponent: int = DEFAULT_EXPONENT) -> typing.Tuple[int, int, int, int]:
|
||||||
|
"""Generate RSA keys of nbits bits. Returns (p, q, e, d).
|
||||||
|
|
||||||
|
Note: this can take a long time, depending on the key size.
|
||||||
|
|
||||||
|
:param nbits: the total number of bits in ``p`` and ``q``. Both ``p`` and
|
||||||
|
``q`` will use ``nbits/2`` bits.
|
||||||
|
:param getprime_func: either :py:func:`rsa.prime.getprime` or a function
|
||||||
|
with similar signature.
|
||||||
|
:param exponent: the exponent for the key; only change this if you know
|
||||||
|
what you're doing, as the exponent influences how difficult your
|
||||||
|
private key can be cracked. A very common choice for e is 65537.
|
||||||
|
:type exponent: int
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Regenerate p and q values, until calculate_keys doesn't raise a
|
||||||
|
# ValueError.
|
||||||
|
while True:
|
||||||
|
(p, q) = find_p_q(nbits // 2, getprime_func, accurate)
|
||||||
|
try:
|
||||||
|
(e, d) = calculate_keys_custom_exponent(p, q, exponent=exponent)
|
||||||
|
break
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return p, q, e, d
|
||||||
|
|
||||||
|
|
||||||
|
def newkeys(nbits: int,
|
||||||
|
accurate: bool = True,
|
||||||
|
poolsize: int = 1,
|
||||||
|
exponent: int = DEFAULT_EXPONENT) -> typing.Tuple[PublicKey, PrivateKey]:
|
||||||
|
"""Generates public and private keys, and returns them as (pub, priv).
|
||||||
|
|
||||||
|
The public key is also known as the 'encryption key', and is a
|
||||||
|
:py:class:`rsa.PublicKey` object. The private key is also known as the
|
||||||
|
'decryption key' and is a :py:class:`rsa.PrivateKey` object.
|
||||||
|
|
||||||
|
:param nbits: the number of bits required to store ``n = p*q``.
|
||||||
|
:param accurate: when True, ``n`` will have exactly the number of bits you
|
||||||
|
asked for. However, this makes key generation much slower. When False,
|
||||||
|
`n`` may have slightly less bits.
|
||||||
|
:param poolsize: the number of processes to use to generate the prime
|
||||||
|
numbers. If set to a number > 1, a parallel algorithm will be used.
|
||||||
|
This requires Python 2.6 or newer.
|
||||||
|
:param exponent: the exponent for the key; only change this if you know
|
||||||
|
what you're doing, as the exponent influences how difficult your
|
||||||
|
private key can be cracked. A very common choice for e is 65537.
|
||||||
|
:type exponent: int
|
||||||
|
|
||||||
|
:returns: a tuple (:py:class:`rsa.PublicKey`, :py:class:`rsa.PrivateKey`)
|
||||||
|
|
||||||
|
The ``poolsize`` parameter was added in *Python-RSA 3.1* and requires
|
||||||
|
Python 2.6 or newer.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if nbits < 16:
|
||||||
|
raise ValueError('Key too small')
|
||||||
|
|
||||||
|
if poolsize < 1:
|
||||||
|
raise ValueError('Pool size (%i) should be >= 1' % poolsize)
|
||||||
|
|
||||||
|
# Determine which getprime function to use
|
||||||
|
if poolsize > 1:
|
||||||
|
from rsa import parallel
|
||||||
|
|
||||||
|
def getprime_func(nbits: int) -> int:
|
||||||
|
return parallel.getprime(nbits, poolsize=poolsize)
|
||||||
|
else:
|
||||||
|
getprime_func = rsa.prime.getprime
|
||||||
|
|
||||||
|
# Generate the key components
|
||||||
|
(p, q, e, d) = gen_keys(nbits, getprime_func, accurate=accurate, exponent=exponent)
|
||||||
|
|
||||||
|
# Create the key objects
|
||||||
|
n = p * q
|
||||||
|
|
||||||
|
return (
|
||||||
|
PublicKey(n, e),
|
||||||
|
PrivateKey(n, e, d, p, q)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['PublicKey', 'PrivateKey', 'newkeys']
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
try:
|
||||||
|
for count in range(100):
|
||||||
|
(failures, tests) = doctest.testmod()
|
||||||
|
if failures:
|
||||||
|
break
|
||||||
|
|
||||||
|
if (count % 10 == 0 and count) or count == 1:
|
||||||
|
print('%i times' % count)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('Aborted')
|
||||||
|
else:
|
||||||
|
print('Doctests done')
|
||||||
97
rsa/parallel.py
Normal file
97
rsa/parallel.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Functions for parallel computation on multiple cores.
|
||||||
|
|
||||||
|
Introduced in Python-RSA 3.1.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Requires Python 2.6 or newer.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import multiprocessing as mp
|
||||||
|
from multiprocessing.connection import Connection
|
||||||
|
|
||||||
|
import rsa.prime
|
||||||
|
import rsa.randnum
|
||||||
|
|
||||||
|
|
||||||
|
def _find_prime(nbits: int, pipe: Connection) -> None:
|
||||||
|
while True:
|
||||||
|
integer = rsa.randnum.read_random_odd_int(nbits)
|
||||||
|
|
||||||
|
# Test for primeness
|
||||||
|
if rsa.prime.is_prime(integer):
|
||||||
|
pipe.send(integer)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def getprime(nbits: int, poolsize: int) -> int:
|
||||||
|
"""Returns a prime number that can be stored in 'nbits' bits.
|
||||||
|
|
||||||
|
Works in multiple threads at the same time.
|
||||||
|
|
||||||
|
>>> p = getprime(128, 3)
|
||||||
|
>>> rsa.prime.is_prime(p-1)
|
||||||
|
False
|
||||||
|
>>> rsa.prime.is_prime(p)
|
||||||
|
True
|
||||||
|
>>> rsa.prime.is_prime(p+1)
|
||||||
|
False
|
||||||
|
|
||||||
|
>>> from rsa import common
|
||||||
|
>>> common.bit_size(p) == 128
|
||||||
|
True
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
(pipe_recv, pipe_send) = mp.Pipe(duplex=False)
|
||||||
|
|
||||||
|
# Create processes
|
||||||
|
try:
|
||||||
|
procs = [mp.Process(target=_find_prime, args=(nbits, pipe_send))
|
||||||
|
for _ in range(poolsize)]
|
||||||
|
# Start processes
|
||||||
|
for p in procs:
|
||||||
|
p.start()
|
||||||
|
|
||||||
|
result = pipe_recv.recv()
|
||||||
|
finally:
|
||||||
|
pipe_recv.close()
|
||||||
|
pipe_send.close()
|
||||||
|
|
||||||
|
# Terminate processes
|
||||||
|
for p in procs:
|
||||||
|
p.terminate()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['getprime']
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print('Running doctests 1000x or until failure')
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
for count in range(100):
|
||||||
|
(failures, tests) = doctest.testmod()
|
||||||
|
if failures:
|
||||||
|
break
|
||||||
|
|
||||||
|
if count % 10 == 0 and count:
|
||||||
|
print('%i times' % count)
|
||||||
|
|
||||||
|
print('Doctests done')
|
||||||
132
rsa/pem.py
Normal file
132
rsa/pem.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Functions that load and write PEM-encoded files."""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import typing
|
||||||
|
|
||||||
|
# Should either be ASCII strings or bytes.
|
||||||
|
FlexiText = typing.Union[str, bytes]
|
||||||
|
|
||||||
|
|
||||||
|
def _markers(pem_marker: FlexiText) -> typing.Tuple[bytes, bytes]:
|
||||||
|
"""
|
||||||
|
Returns the start and end PEM markers, as bytes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(pem_marker, bytes):
|
||||||
|
pem_marker = pem_marker.encode('ascii')
|
||||||
|
|
||||||
|
return (b'-----BEGIN ' + pem_marker + b'-----',
|
||||||
|
b'-----END ' + pem_marker + b'-----')
|
||||||
|
|
||||||
|
|
||||||
|
def _pem_lines(contents: bytes, pem_start: bytes, pem_end: bytes) -> typing.Iterator[bytes]:
|
||||||
|
"""Generator over PEM lines between pem_start and pem_end."""
|
||||||
|
|
||||||
|
in_pem_part = False
|
||||||
|
seen_pem_start = False
|
||||||
|
|
||||||
|
for line in contents.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
# Skip empty lines
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Handle start marker
|
||||||
|
if line == pem_start:
|
||||||
|
if in_pem_part:
|
||||||
|
raise ValueError('Seen start marker "%r" twice' % pem_start)
|
||||||
|
|
||||||
|
in_pem_part = True
|
||||||
|
seen_pem_start = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Skip stuff before first marker
|
||||||
|
if not in_pem_part:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Handle end marker
|
||||||
|
if in_pem_part and line == pem_end:
|
||||||
|
in_pem_part = False
|
||||||
|
break
|
||||||
|
|
||||||
|
# Load fields
|
||||||
|
if b':' in line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
yield line
|
||||||
|
|
||||||
|
# Do some sanity checks
|
||||||
|
if not seen_pem_start:
|
||||||
|
raise ValueError('No PEM start marker "%r" found' % pem_start)
|
||||||
|
|
||||||
|
if in_pem_part:
|
||||||
|
raise ValueError('No PEM end marker "%r" found' % pem_end)
|
||||||
|
|
||||||
|
|
||||||
|
def load_pem(contents: FlexiText, pem_marker: FlexiText) -> bytes:
|
||||||
|
"""Loads a PEM file.
|
||||||
|
|
||||||
|
:param contents: the contents of the file to interpret
|
||||||
|
:param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY'
|
||||||
|
when your file has '-----BEGIN RSA PRIVATE KEY-----' and
|
||||||
|
'-----END RSA PRIVATE KEY-----' markers.
|
||||||
|
|
||||||
|
:return: the base64-decoded content between the start and end markers.
|
||||||
|
|
||||||
|
@raise ValueError: when the content is invalid, for example when the start
|
||||||
|
marker cannot be found.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# We want bytes, not text. If it's text, it can be converted to ASCII bytes.
|
||||||
|
if not isinstance(contents, bytes):
|
||||||
|
contents = contents.encode('ascii')
|
||||||
|
|
||||||
|
(pem_start, pem_end) = _markers(pem_marker)
|
||||||
|
pem_lines = [line for line in _pem_lines(contents, pem_start, pem_end)]
|
||||||
|
|
||||||
|
# Base64-decode the contents
|
||||||
|
pem = b''.join(pem_lines)
|
||||||
|
return base64.standard_b64decode(pem)
|
||||||
|
|
||||||
|
|
||||||
|
def save_pem(contents: bytes, pem_marker: FlexiText) -> bytes:
|
||||||
|
"""Saves a PEM file.
|
||||||
|
|
||||||
|
:param contents: the contents to encode in PEM format
|
||||||
|
:param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY'
|
||||||
|
when your file has '-----BEGIN RSA PRIVATE KEY-----' and
|
||||||
|
'-----END RSA PRIVATE KEY-----' markers.
|
||||||
|
|
||||||
|
:return: the base64-encoded content between the start and end markers, as bytes.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
(pem_start, pem_end) = _markers(pem_marker)
|
||||||
|
|
||||||
|
b64 = base64.standard_b64encode(contents).replace(b'\n', b'')
|
||||||
|
pem_lines = [pem_start]
|
||||||
|
|
||||||
|
for block_start in range(0, len(b64), 64):
|
||||||
|
block = b64[block_start:block_start + 64]
|
||||||
|
pem_lines.append(block)
|
||||||
|
|
||||||
|
pem_lines.append(pem_end)
|
||||||
|
pem_lines.append(b'')
|
||||||
|
|
||||||
|
return b'\n'.join(pem_lines)
|
||||||
470
rsa/pkcs1.py
Normal file
470
rsa/pkcs1.py
Normal file
@ -0,0 +1,470 @@
|
|||||||
|
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Functions for PKCS#1 version 1.5 encryption and signing
|
||||||
|
|
||||||
|
This module implements certain functionality from PKCS#1 version 1.5. For a
|
||||||
|
very clear example, read http://www.di-mgt.com.au/rsa_alg.html#pkcs1schemes
|
||||||
|
|
||||||
|
At least 8 bytes of random padding is used when encrypting a message. This makes
|
||||||
|
these methods much more secure than the ones in the ``rsa`` module.
|
||||||
|
|
||||||
|
WARNING: this module leaks information when decryption fails. The exceptions
|
||||||
|
that are raised contain the Python traceback information, which can be used to
|
||||||
|
deduce where in the process the failure occurred. DO NOT PASS SUCH INFORMATION
|
||||||
|
to your users.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import typing
|
||||||
|
from hmac import compare_digest
|
||||||
|
|
||||||
|
from . import common, transform, core, key
|
||||||
|
|
||||||
|
# ASN.1 codes that describe the hash algorithm used.
|
||||||
|
HASH_ASN1 = {
|
||||||
|
'MD5': b'\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10',
|
||||||
|
'SHA-1': b'\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14',
|
||||||
|
'SHA-224': b'\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c',
|
||||||
|
'SHA-256': b'\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20',
|
||||||
|
'SHA-384': b'\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30',
|
||||||
|
'SHA-512': b'\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40',
|
||||||
|
}
|
||||||
|
|
||||||
|
HASH_METHODS = {
|
||||||
|
'MD5': hashlib.md5,
|
||||||
|
'SHA-1': hashlib.sha1,
|
||||||
|
'SHA-224': hashlib.sha224,
|
||||||
|
'SHA-256': hashlib.sha256,
|
||||||
|
'SHA-384': hashlib.sha384,
|
||||||
|
'SHA-512': hashlib.sha512,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 6):
|
||||||
|
# Python 3.6 introduced SHA3 support.
|
||||||
|
HASH_ASN1.update({
|
||||||
|
'SHA3-256': b'\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x08\x05\x00\x04\x20',
|
||||||
|
'SHA3-384': b'\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x09\x05\x00\x04\x30',
|
||||||
|
'SHA3-512': b'\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x0a\x05\x00\x04\x40',
|
||||||
|
})
|
||||||
|
|
||||||
|
HASH_METHODS.update({
|
||||||
|
'SHA3-256': hashlib.sha3_256,
|
||||||
|
'SHA3-384': hashlib.sha3_384,
|
||||||
|
'SHA3-512': hashlib.sha3_512,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class CryptoError(Exception):
|
||||||
|
"""Base class for all exceptions in this module."""
|
||||||
|
|
||||||
|
|
||||||
|
class DecryptionError(CryptoError):
|
||||||
|
"""Raised when decryption fails."""
|
||||||
|
|
||||||
|
|
||||||
|
class VerificationError(CryptoError):
|
||||||
|
"""Raised when verification fails."""
|
||||||
|
|
||||||
|
|
||||||
|
def _pad_for_encryption(message: bytes, target_length: int) -> bytes:
|
||||||
|
r"""Pads the message for encryption, returning the padded message.
|
||||||
|
|
||||||
|
:return: 00 02 RANDOM_DATA 00 MESSAGE
|
||||||
|
|
||||||
|
>>> block = _pad_for_encryption(b'hello', 16)
|
||||||
|
>>> len(block)
|
||||||
|
16
|
||||||
|
>>> block[0:2]
|
||||||
|
b'\x00\x02'
|
||||||
|
>>> block[-6:]
|
||||||
|
b'\x00hello'
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
max_msglength = target_length - 11
|
||||||
|
msglength = len(message)
|
||||||
|
|
||||||
|
if msglength > max_msglength:
|
||||||
|
raise OverflowError('%i bytes needed for message, but there is only'
|
||||||
|
' space for %i' % (msglength, max_msglength))
|
||||||
|
|
||||||
|
# Get random padding
|
||||||
|
padding = b''
|
||||||
|
padding_length = target_length - msglength - 3
|
||||||
|
|
||||||
|
# We remove 0-bytes, so we'll end up with less padding than we've asked for,
|
||||||
|
# so keep adding data until we're at the correct length.
|
||||||
|
while len(padding) < padding_length:
|
||||||
|
needed_bytes = padding_length - len(padding)
|
||||||
|
|
||||||
|
# Always read at least 8 bytes more than we need, and trim off the rest
|
||||||
|
# after removing the 0-bytes. This increases the chance of getting
|
||||||
|
# enough bytes, especially when needed_bytes is small
|
||||||
|
new_padding = os.urandom(needed_bytes + 5)
|
||||||
|
new_padding = new_padding.replace(b'\x00', b'')
|
||||||
|
padding = padding + new_padding[:needed_bytes]
|
||||||
|
|
||||||
|
assert len(padding) == padding_length
|
||||||
|
|
||||||
|
return b''.join([b'\x00\x02',
|
||||||
|
padding,
|
||||||
|
b'\x00',
|
||||||
|
message])
|
||||||
|
|
||||||
|
|
||||||
|
def _pad_for_signing(message: bytes, target_length: int) -> bytes:
|
||||||
|
r"""Pads the message for signing, returning the padded message.
|
||||||
|
|
||||||
|
The padding is always a repetition of FF bytes.
|
||||||
|
|
||||||
|
:return: 00 01 PADDING 00 MESSAGE
|
||||||
|
|
||||||
|
>>> block = _pad_for_signing(b'hello', 16)
|
||||||
|
>>> len(block)
|
||||||
|
16
|
||||||
|
>>> block[0:2]
|
||||||
|
b'\x00\x01'
|
||||||
|
>>> block[-6:]
|
||||||
|
b'\x00hello'
|
||||||
|
>>> block[2:-6]
|
||||||
|
b'\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
max_msglength = target_length - 11
|
||||||
|
msglength = len(message)
|
||||||
|
|
||||||
|
if msglength > max_msglength:
|
||||||
|
raise OverflowError('%i bytes needed for message, but there is only'
|
||||||
|
' space for %i' % (msglength, max_msglength))
|
||||||
|
|
||||||
|
padding_length = target_length - msglength - 3
|
||||||
|
|
||||||
|
return b''.join([b'\x00\x01',
|
||||||
|
padding_length * b'\xff',
|
||||||
|
b'\x00',
|
||||||
|
message])
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt(message: bytes, pub_key: key.PublicKey) -> bytes:
|
||||||
|
"""Encrypts the given message using PKCS#1 v1.5
|
||||||
|
|
||||||
|
:param message: the message to encrypt. Must be a byte string no longer than
|
||||||
|
``k-11`` bytes, where ``k`` is the number of bytes needed to encode
|
||||||
|
the ``n`` component of the public key.
|
||||||
|
:param pub_key: the :py:class:`rsa.PublicKey` to encrypt with.
|
||||||
|
:raise OverflowError: when the message is too large to fit in the padded
|
||||||
|
block.
|
||||||
|
|
||||||
|
>>> from rsa import key, common
|
||||||
|
>>> (pub_key, priv_key) = key.newkeys(256)
|
||||||
|
>>> message = b'hello'
|
||||||
|
>>> crypto = encrypt(message, pub_key)
|
||||||
|
|
||||||
|
The crypto text should be just as long as the public key 'n' component:
|
||||||
|
|
||||||
|
>>> len(crypto) == common.byte_size(pub_key.n)
|
||||||
|
True
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
keylength = common.byte_size(pub_key.n)
|
||||||
|
padded = _pad_for_encryption(message, keylength)
|
||||||
|
|
||||||
|
payload = transform.bytes2int(padded)
|
||||||
|
encrypted = core.encrypt_int(payload, pub_key.e, pub_key.n)
|
||||||
|
block = transform.int2bytes(encrypted, keylength)
|
||||||
|
|
||||||
|
return block
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes:
|
||||||
|
r"""Decrypts the given message using PKCS#1 v1.5
|
||||||
|
|
||||||
|
The decryption is considered 'failed' when the resulting cleartext doesn't
|
||||||
|
start with the bytes 00 02, or when the 00 byte between the padding and
|
||||||
|
the message cannot be found.
|
||||||
|
|
||||||
|
:param crypto: the crypto text as returned by :py:func:`rsa.encrypt`
|
||||||
|
:param priv_key: the :py:class:`rsa.PrivateKey` to decrypt with.
|
||||||
|
:raise DecryptionError: when the decryption fails. No details are given as
|
||||||
|
to why the code thinks the decryption fails, as this would leak
|
||||||
|
information about the private key.
|
||||||
|
|
||||||
|
|
||||||
|
>>> import rsa
|
||||||
|
>>> (pub_key, priv_key) = rsa.newkeys(256)
|
||||||
|
|
||||||
|
It works with strings:
|
||||||
|
|
||||||
|
>>> crypto = encrypt(b'hello', pub_key)
|
||||||
|
>>> decrypt(crypto, priv_key)
|
||||||
|
b'hello'
|
||||||
|
|
||||||
|
And with binary data:
|
||||||
|
|
||||||
|
>>> crypto = encrypt(b'\x00\x00\x00\x00\x01', pub_key)
|
||||||
|
>>> decrypt(crypto, priv_key)
|
||||||
|
b'\x00\x00\x00\x00\x01'
|
||||||
|
|
||||||
|
Altering the encrypted information will *likely* cause a
|
||||||
|
:py:class:`rsa.pkcs1.DecryptionError`. If you want to be *sure*, use
|
||||||
|
:py:func:`rsa.sign`.
|
||||||
|
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Never display the stack trace of a
|
||||||
|
:py:class:`rsa.pkcs1.DecryptionError` exception. It shows where in the
|
||||||
|
code the exception occurred, and thus leaks information about the key.
|
||||||
|
It's only a tiny bit of information, but every bit makes cracking the
|
||||||
|
keys easier.
|
||||||
|
|
||||||
|
>>> crypto = encrypt(b'hello', pub_key)
|
||||||
|
>>> crypto = crypto[0:5] + b'X' + crypto[6:] # change a byte
|
||||||
|
>>> decrypt(crypto, priv_key)
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
rsa.pkcs1.DecryptionError: Decryption failed
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
blocksize = common.byte_size(priv_key.n)
|
||||||
|
encrypted = transform.bytes2int(crypto)
|
||||||
|
decrypted = priv_key.blinded_decrypt(encrypted)
|
||||||
|
cleartext = transform.int2bytes(decrypted, blocksize)
|
||||||
|
|
||||||
|
# Detect leading zeroes in the crypto. These are not reflected in the
|
||||||
|
# encrypted value (as leading zeroes do not influence the value of an
|
||||||
|
# integer). This fixes CVE-2020-13757.
|
||||||
|
if len(crypto) > blocksize:
|
||||||
|
# This is operating on public information, so doesn't need to be constant-time.
|
||||||
|
raise DecryptionError('Decryption failed')
|
||||||
|
|
||||||
|
# If we can't find the cleartext marker, decryption failed.
|
||||||
|
cleartext_marker_bad = not compare_digest(cleartext[:2], b'\x00\x02')
|
||||||
|
|
||||||
|
# Find the 00 separator between the padding and the message
|
||||||
|
sep_idx = cleartext.find(b'\x00', 2)
|
||||||
|
|
||||||
|
# sep_idx indicates the position of the `\x00` separator that separates the
|
||||||
|
# padding from the actual message. The padding should be at least 8 bytes
|
||||||
|
# long (see https://tools.ietf.org/html/rfc8017#section-7.2.2 step 3), which
|
||||||
|
# means the separator should be at least at index 10 (because of the
|
||||||
|
# `\x00\x02` marker that preceeds it).
|
||||||
|
sep_idx_bad = sep_idx < 10
|
||||||
|
|
||||||
|
anything_bad = cleartext_marker_bad | sep_idx_bad
|
||||||
|
if anything_bad:
|
||||||
|
raise DecryptionError('Decryption failed')
|
||||||
|
|
||||||
|
return cleartext[sep_idx + 1:]
|
||||||
|
|
||||||
|
|
||||||
|
def sign_hash(hash_value: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes:
|
||||||
|
"""Signs a precomputed hash with the private key.
|
||||||
|
|
||||||
|
Hashes the message, then signs the hash with the given key. This is known
|
||||||
|
as a "detached signature", because the message itself isn't altered.
|
||||||
|
|
||||||
|
:param hash_value: A precomputed hash to sign (ignores message).
|
||||||
|
:param priv_key: the :py:class:`rsa.PrivateKey` to sign with
|
||||||
|
:param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
|
||||||
|
'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
|
||||||
|
:return: a message signature block.
|
||||||
|
:raise OverflowError: if the private key is too small to contain the
|
||||||
|
requested hash.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Get the ASN1 code for this hash method
|
||||||
|
if hash_method not in HASH_ASN1:
|
||||||
|
raise ValueError('Invalid hash method: %s' % hash_method)
|
||||||
|
asn1code = HASH_ASN1[hash_method]
|
||||||
|
|
||||||
|
# Encrypt the hash with the private key
|
||||||
|
cleartext = asn1code + hash_value
|
||||||
|
keylength = common.byte_size(priv_key.n)
|
||||||
|
padded = _pad_for_signing(cleartext, keylength)
|
||||||
|
|
||||||
|
payload = transform.bytes2int(padded)
|
||||||
|
encrypted = priv_key.blinded_encrypt(payload)
|
||||||
|
block = transform.int2bytes(encrypted, keylength)
|
||||||
|
|
||||||
|
return block
|
||||||
|
|
||||||
|
|
||||||
|
def sign(message: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes:
|
||||||
|
"""Signs the message with the private key.
|
||||||
|
|
||||||
|
Hashes the message, then signs the hash with the given key. This is known
|
||||||
|
as a "detached signature", because the message itself isn't altered.
|
||||||
|
|
||||||
|
:param message: the message to sign. Can be an 8-bit string or a file-like
|
||||||
|
object. If ``message`` has a ``read()`` method, it is assumed to be a
|
||||||
|
file-like object.
|
||||||
|
:param priv_key: the :py:class:`rsa.PrivateKey` to sign with
|
||||||
|
:param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
|
||||||
|
'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
|
||||||
|
:return: a message signature block.
|
||||||
|
:raise OverflowError: if the private key is too small to contain the
|
||||||
|
requested hash.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
msg_hash = compute_hash(message, hash_method)
|
||||||
|
return sign_hash(msg_hash, priv_key, hash_method)
|
||||||
|
|
||||||
|
|
||||||
|
def verify(message: bytes, signature: bytes, pub_key: key.PublicKey) -> str:
|
||||||
|
"""Verifies that the signature matches the message.
|
||||||
|
|
||||||
|
The hash method is detected automatically from the signature.
|
||||||
|
|
||||||
|
:param message: the signed message. Can be an 8-bit string or a file-like
|
||||||
|
object. If ``message`` has a ``read()`` method, it is assumed to be a
|
||||||
|
file-like object.
|
||||||
|
:param signature: the signature block, as created with :py:func:`rsa.sign`.
|
||||||
|
:param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
|
||||||
|
:raise VerificationError: when the signature doesn't match the message.
|
||||||
|
:returns: the name of the used hash.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
keylength = common.byte_size(pub_key.n)
|
||||||
|
encrypted = transform.bytes2int(signature)
|
||||||
|
decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
|
||||||
|
clearsig = transform.int2bytes(decrypted, keylength)
|
||||||
|
|
||||||
|
# Get the hash method
|
||||||
|
method_name = _find_method_hash(clearsig)
|
||||||
|
message_hash = compute_hash(message, method_name)
|
||||||
|
|
||||||
|
# Reconstruct the expected padded hash
|
||||||
|
cleartext = HASH_ASN1[method_name] + message_hash
|
||||||
|
expected = _pad_for_signing(cleartext, keylength)
|
||||||
|
|
||||||
|
if len(signature) != keylength:
|
||||||
|
raise VerificationError('Verification failed')
|
||||||
|
|
||||||
|
# Compare with the signed one
|
||||||
|
if expected != clearsig:
|
||||||
|
raise VerificationError('Verification failed')
|
||||||
|
|
||||||
|
return method_name
|
||||||
|
|
||||||
|
|
||||||
|
def find_signature_hash(signature: bytes, pub_key: key.PublicKey) -> str:
|
||||||
|
"""Returns the hash name detected from the signature.
|
||||||
|
|
||||||
|
If you also want to verify the message, use :py:func:`rsa.verify()` instead.
|
||||||
|
It also returns the name of the used hash.
|
||||||
|
|
||||||
|
:param signature: the signature block, as created with :py:func:`rsa.sign`.
|
||||||
|
:param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
|
||||||
|
:returns: the name of the used hash.
|
||||||
|
"""
|
||||||
|
|
||||||
|
keylength = common.byte_size(pub_key.n)
|
||||||
|
encrypted = transform.bytes2int(signature)
|
||||||
|
decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
|
||||||
|
clearsig = transform.int2bytes(decrypted, keylength)
|
||||||
|
|
||||||
|
return _find_method_hash(clearsig)
|
||||||
|
|
||||||
|
|
||||||
|
def yield_fixedblocks(infile: typing.BinaryIO, blocksize: int) -> typing.Iterator[bytes]:
|
||||||
|
"""Generator, yields each block of ``blocksize`` bytes in the input file.
|
||||||
|
|
||||||
|
:param infile: file to read and separate in blocks.
|
||||||
|
:param blocksize: block size in bytes.
|
||||||
|
:returns: a generator that yields the contents of each block
|
||||||
|
"""
|
||||||
|
|
||||||
|
while True:
|
||||||
|
block = infile.read(blocksize)
|
||||||
|
|
||||||
|
read_bytes = len(block)
|
||||||
|
if read_bytes == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
yield block
|
||||||
|
|
||||||
|
if read_bytes < blocksize:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def compute_hash(message: typing.Union[bytes, typing.BinaryIO], method_name: str) -> bytes:
|
||||||
|
"""Returns the message digest.
|
||||||
|
|
||||||
|
:param message: the signed message. Can be an 8-bit string or a file-like
|
||||||
|
object. If ``message`` has a ``read()`` method, it is assumed to be a
|
||||||
|
file-like object.
|
||||||
|
:param method_name: the hash method, must be a key of
|
||||||
|
:py:const:`HASH_METHODS`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if method_name not in HASH_METHODS:
|
||||||
|
raise ValueError('Invalid hash method: %s' % method_name)
|
||||||
|
|
||||||
|
method = HASH_METHODS[method_name]
|
||||||
|
hasher = method()
|
||||||
|
|
||||||
|
if isinstance(message, bytes):
|
||||||
|
hasher.update(message)
|
||||||
|
else:
|
||||||
|
assert hasattr(message, 'read') and hasattr(message.read, '__call__')
|
||||||
|
# read as 1K blocks
|
||||||
|
for block in yield_fixedblocks(message, 1024):
|
||||||
|
hasher.update(block)
|
||||||
|
|
||||||
|
return hasher.digest()
|
||||||
|
|
||||||
|
|
||||||
|
def _find_method_hash(clearsig: bytes) -> str:
|
||||||
|
"""Finds the hash method.
|
||||||
|
|
||||||
|
:param clearsig: full padded ASN1 and hash.
|
||||||
|
:return: the used hash method.
|
||||||
|
:raise VerificationFailed: when the hash method cannot be found
|
||||||
|
"""
|
||||||
|
|
||||||
|
for (hashname, asn1code) in HASH_ASN1.items():
|
||||||
|
if asn1code in clearsig:
|
||||||
|
return hashname
|
||||||
|
|
||||||
|
raise VerificationError('Verification failed')
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['encrypt', 'decrypt', 'sign', 'verify',
|
||||||
|
'DecryptionError', 'VerificationError', 'CryptoError']
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print('Running doctests 1000x or until failure')
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
for count in range(1000):
|
||||||
|
(failures, tests) = doctest.testmod()
|
||||||
|
if failures:
|
||||||
|
break
|
||||||
|
|
||||||
|
if count % 100 == 0 and count:
|
||||||
|
print('%i times' % count)
|
||||||
|
|
||||||
|
print('Doctests done')
|
||||||
100
rsa/pkcs1_v2.py
Normal file
100
rsa/pkcs1_v2.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Functions for PKCS#1 version 2 encryption and signing
|
||||||
|
|
||||||
|
This module implements certain functionality from PKCS#1 version 2. Main
|
||||||
|
documentation is RFC 2437: https://tools.ietf.org/html/rfc2437
|
||||||
|
"""
|
||||||
|
|
||||||
|
from rsa import (
|
||||||
|
common,
|
||||||
|
pkcs1,
|
||||||
|
transform,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def mgf1(seed: bytes, length: int, hasher: str = 'SHA-1') -> bytes:
|
||||||
|
"""
|
||||||
|
MGF1 is a Mask Generation Function based on a hash function.
|
||||||
|
|
||||||
|
A mask generation function takes an octet string of variable length and a
|
||||||
|
desired output length as input, and outputs an octet string of the desired
|
||||||
|
length. The plaintext-awareness of RSAES-OAEP relies on the random nature of
|
||||||
|
the output of the mask generation function, which in turn relies on the
|
||||||
|
random nature of the underlying hash.
|
||||||
|
|
||||||
|
:param bytes seed: seed from which mask is generated, an octet string
|
||||||
|
:param int length: intended length in octets of the mask, at most 2^32(hLen)
|
||||||
|
:param str hasher: hash function (hLen denotes the length in octets of the hash
|
||||||
|
function output)
|
||||||
|
|
||||||
|
:return: mask, an octet string of length `length`
|
||||||
|
:rtype: bytes
|
||||||
|
|
||||||
|
:raise OverflowError: when `length` is too large for the specified `hasher`
|
||||||
|
:raise ValueError: when specified `hasher` is invalid
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
hash_length = pkcs1.HASH_METHODS[hasher]().digest_size
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError(
|
||||||
|
'Invalid `hasher` specified. Please select one of: {hash_list}'.format(
|
||||||
|
hash_list=', '.join(sorted(pkcs1.HASH_METHODS.keys()))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# If l > 2^32(hLen), output "mask too long" and stop.
|
||||||
|
if length > (2**32 * hash_length):
|
||||||
|
raise OverflowError(
|
||||||
|
"Desired length should be at most 2**32 times the hasher's output "
|
||||||
|
"length ({hash_length} for {hasher} function)".format(
|
||||||
|
hash_length=hash_length,
|
||||||
|
hasher=hasher,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Looping `counter` from 0 to ceil(l / hLen)-1, build `output` based on the
|
||||||
|
# hashes formed by (`seed` + C), being `C` an octet string of length 4
|
||||||
|
# generated by converting `counter` with the primitive I2OSP
|
||||||
|
output = b''.join(
|
||||||
|
pkcs1.compute_hash(
|
||||||
|
seed + transform.int2bytes(counter, fill_size=4),
|
||||||
|
method_name=hasher,
|
||||||
|
)
|
||||||
|
for counter in range(common.ceil_div(length, hash_length) + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Output the leading `length` octets of `output` as the octet string mask.
|
||||||
|
return output[:length]
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'mgf1',
|
||||||
|
]
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print('Running doctests 1000x or until failure')
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
for count in range(1000):
|
||||||
|
(failures, tests) = doctest.testmod()
|
||||||
|
if failures:
|
||||||
|
break
|
||||||
|
|
||||||
|
if count % 100 == 0 and count:
|
||||||
|
print('%i times' % count)
|
||||||
|
|
||||||
|
print('Doctests done')
|
||||||
198
rsa/prime.py
Normal file
198
rsa/prime.py
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Numerical functions related to primes.
|
||||||
|
|
||||||
|
Implementation based on the book Algorithm Design by Michael T. Goodrich and
|
||||||
|
Roberto Tamassia, 2002.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import rsa.common
|
||||||
|
import rsa.randnum
|
||||||
|
|
||||||
|
__all__ = ['getprime', 'are_relatively_prime']
|
||||||
|
|
||||||
|
|
||||||
|
def gcd(p: int, q: int) -> int:
|
||||||
|
"""Returns the greatest common divisor of p and q
|
||||||
|
|
||||||
|
>>> gcd(48, 180)
|
||||||
|
12
|
||||||
|
"""
|
||||||
|
|
||||||
|
while q != 0:
|
||||||
|
(p, q) = (q, p % q)
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def get_primality_testing_rounds(number: int) -> int:
|
||||||
|
"""Returns minimum number of rounds for Miller-Rabing primality testing,
|
||||||
|
based on number bitsize.
|
||||||
|
|
||||||
|
According to NIST FIPS 186-4, Appendix C, Table C.3, minimum number of
|
||||||
|
rounds of M-R testing, using an error probability of 2 ** (-100), for
|
||||||
|
different p, q bitsizes are:
|
||||||
|
* p, q bitsize: 512; rounds: 7
|
||||||
|
* p, q bitsize: 1024; rounds: 4
|
||||||
|
* p, q bitsize: 1536; rounds: 3
|
||||||
|
See: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Calculate number bitsize.
|
||||||
|
bitsize = rsa.common.bit_size(number)
|
||||||
|
# Set number of rounds.
|
||||||
|
if bitsize >= 1536:
|
||||||
|
return 3
|
||||||
|
if bitsize >= 1024:
|
||||||
|
return 4
|
||||||
|
if bitsize >= 512:
|
||||||
|
return 7
|
||||||
|
# For smaller bitsizes, set arbitrary number of rounds.
|
||||||
|
return 10
|
||||||
|
|
||||||
|
|
||||||
|
def miller_rabin_primality_testing(n: int, k: int) -> bool:
|
||||||
|
"""Calculates whether n is composite (which is always correct) or prime
|
||||||
|
(which theoretically is incorrect with error probability 4**-k), by
|
||||||
|
applying Miller-Rabin primality testing.
|
||||||
|
|
||||||
|
For reference and implementation example, see:
|
||||||
|
https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test
|
||||||
|
|
||||||
|
:param n: Integer to be tested for primality.
|
||||||
|
:type n: int
|
||||||
|
:param k: Number of rounds (witnesses) of Miller-Rabin testing.
|
||||||
|
:type k: int
|
||||||
|
:return: False if the number is composite, True if it's probably prime.
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
|
||||||
|
# prevent potential infinite loop when d = 0
|
||||||
|
if n < 2:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Decompose (n - 1) to write it as (2 ** r) * d
|
||||||
|
# While d is even, divide it by 2 and increase the exponent.
|
||||||
|
d = n - 1
|
||||||
|
r = 0
|
||||||
|
|
||||||
|
while not (d & 1):
|
||||||
|
r += 1
|
||||||
|
d >>= 1
|
||||||
|
|
||||||
|
# Test k witnesses.
|
||||||
|
for _ in range(k):
|
||||||
|
# Generate random integer a, where 2 <= a <= (n - 2)
|
||||||
|
a = rsa.randnum.randint(n - 3) + 1
|
||||||
|
|
||||||
|
x = pow(a, d, n)
|
||||||
|
if x == 1 or x == n - 1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for _ in range(r - 1):
|
||||||
|
x = pow(x, 2, n)
|
||||||
|
if x == 1:
|
||||||
|
# n is composite.
|
||||||
|
return False
|
||||||
|
if x == n - 1:
|
||||||
|
# Exit inner loop and continue with next witness.
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# If loop doesn't break, n is composite.
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def is_prime(number: int) -> bool:
|
||||||
|
"""Returns True if the number is prime, and False otherwise.
|
||||||
|
|
||||||
|
>>> is_prime(2)
|
||||||
|
True
|
||||||
|
>>> is_prime(42)
|
||||||
|
False
|
||||||
|
>>> is_prime(41)
|
||||||
|
True
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check for small numbers.
|
||||||
|
if number < 10:
|
||||||
|
return number in {2, 3, 5, 7}
|
||||||
|
|
||||||
|
# Check for even numbers.
|
||||||
|
if not (number & 1):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Calculate minimum number of rounds.
|
||||||
|
k = get_primality_testing_rounds(number)
|
||||||
|
|
||||||
|
# Run primality testing with (minimum + 1) rounds.
|
||||||
|
return miller_rabin_primality_testing(number, k + 1)
|
||||||
|
|
||||||
|
|
||||||
|
def getprime(nbits: int) -> int:
|
||||||
|
"""Returns a prime number that can be stored in 'nbits' bits.
|
||||||
|
|
||||||
|
>>> p = getprime(128)
|
||||||
|
>>> is_prime(p-1)
|
||||||
|
False
|
||||||
|
>>> is_prime(p)
|
||||||
|
True
|
||||||
|
>>> is_prime(p+1)
|
||||||
|
False
|
||||||
|
|
||||||
|
>>> from rsa import common
|
||||||
|
>>> common.bit_size(p) == 128
|
||||||
|
True
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert nbits > 3 # the loop wil hang on too small numbers
|
||||||
|
|
||||||
|
while True:
|
||||||
|
integer = rsa.randnum.read_random_odd_int(nbits)
|
||||||
|
|
||||||
|
# Test for primeness
|
||||||
|
if is_prime(integer):
|
||||||
|
return integer
|
||||||
|
|
||||||
|
# Retry if not prime
|
||||||
|
|
||||||
|
|
||||||
|
def are_relatively_prime(a: int, b: int) -> bool:
|
||||||
|
"""Returns True if a and b are relatively prime, and False if they
|
||||||
|
are not.
|
||||||
|
|
||||||
|
>>> are_relatively_prime(2, 3)
|
||||||
|
True
|
||||||
|
>>> are_relatively_prime(2, 4)
|
||||||
|
False
|
||||||
|
"""
|
||||||
|
|
||||||
|
d = gcd(a, b)
|
||||||
|
return d == 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print('Running doctests 1000x or until failure')
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
for count in range(1000):
|
||||||
|
(failures, tests) = doctest.testmod()
|
||||||
|
if failures:
|
||||||
|
break
|
||||||
|
|
||||||
|
if count % 100 == 0 and count:
|
||||||
|
print('%i times' % count)
|
||||||
|
|
||||||
|
print('Doctests done')
|
||||||
96
rsa/randnum.py
Normal file
96
rsa/randnum.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Functions for generating random numbers."""
|
||||||
|
|
||||||
|
# Source inspired by code by Yesudeep Mangalapilly <yesudeep@gmail.com>
|
||||||
|
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from rsa import common, transform
|
||||||
|
|
||||||
|
|
||||||
|
def read_random_bits(nbits: int) -> bytes:
|
||||||
|
"""Reads 'nbits' random bits.
|
||||||
|
|
||||||
|
If nbits isn't a whole number of bytes, an extra byte will be appended with
|
||||||
|
only the lower bits set.
|
||||||
|
"""
|
||||||
|
|
||||||
|
nbytes, rbits = divmod(nbits, 8)
|
||||||
|
|
||||||
|
# Get the random bytes
|
||||||
|
randomdata = os.urandom(nbytes)
|
||||||
|
|
||||||
|
# Add the remaining random bits
|
||||||
|
if rbits > 0:
|
||||||
|
randomvalue = ord(os.urandom(1))
|
||||||
|
randomvalue >>= (8 - rbits)
|
||||||
|
randomdata = struct.pack("B", randomvalue) + randomdata
|
||||||
|
|
||||||
|
return randomdata
|
||||||
|
|
||||||
|
|
||||||
|
def read_random_int(nbits: int) -> int:
|
||||||
|
"""Reads a random integer of approximately nbits bits.
|
||||||
|
"""
|
||||||
|
|
||||||
|
randomdata = read_random_bits(nbits)
|
||||||
|
value = transform.bytes2int(randomdata)
|
||||||
|
|
||||||
|
# Ensure that the number is large enough to just fill out the required
|
||||||
|
# number of bits.
|
||||||
|
value |= 1 << (nbits - 1)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def read_random_odd_int(nbits: int) -> int:
|
||||||
|
"""Reads a random odd integer of approximately nbits bits.
|
||||||
|
|
||||||
|
>>> read_random_odd_int(512) & 1
|
||||||
|
1
|
||||||
|
"""
|
||||||
|
|
||||||
|
value = read_random_int(nbits)
|
||||||
|
|
||||||
|
# Make sure it's odd
|
||||||
|
return value | 1
|
||||||
|
|
||||||
|
|
||||||
|
def randint(maxvalue: int) -> int:
|
||||||
|
"""Returns a random integer x with 1 <= x <= maxvalue
|
||||||
|
|
||||||
|
May take a very long time in specific situations. If maxvalue needs N bits
|
||||||
|
to store, the closer maxvalue is to (2 ** N) - 1, the faster this function
|
||||||
|
is.
|
||||||
|
"""
|
||||||
|
|
||||||
|
bit_size = common.bit_size(maxvalue)
|
||||||
|
|
||||||
|
tries = 0
|
||||||
|
while True:
|
||||||
|
value = read_random_int(bit_size)
|
||||||
|
if value <= maxvalue:
|
||||||
|
break
|
||||||
|
|
||||||
|
if tries % 10 == 0 and tries:
|
||||||
|
# After a lot of tries to get the right number of bits but still
|
||||||
|
# smaller than maxvalue, decrease the number of bits by 1. That'll
|
||||||
|
# dramatically increase the chances to get a large enough number.
|
||||||
|
bit_size -= 1
|
||||||
|
tries += 1
|
||||||
|
|
||||||
|
return value
|
||||||
72
rsa/transform.py
Normal file
72
rsa/transform.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Data transformation functions.
|
||||||
|
|
||||||
|
From bytes to a number, number to bytes, etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
def bytes2int(raw_bytes: bytes) -> int:
|
||||||
|
r"""Converts a list of bytes or an 8-bit string to an integer.
|
||||||
|
|
||||||
|
When using unicode strings, encode it to some encoding like UTF8 first.
|
||||||
|
|
||||||
|
>>> (((128 * 256) + 64) * 256) + 15
|
||||||
|
8405007
|
||||||
|
>>> bytes2int(b'\x80@\x0f')
|
||||||
|
8405007
|
||||||
|
|
||||||
|
"""
|
||||||
|
return int.from_bytes(raw_bytes, 'big', signed=False)
|
||||||
|
|
||||||
|
|
||||||
|
def int2bytes(number: int, fill_size: int = 0) -> bytes:
|
||||||
|
"""
|
||||||
|
Convert an unsigned integer to bytes (big-endian)::
|
||||||
|
|
||||||
|
Does not preserve leading zeros if you don't specify a fill size.
|
||||||
|
|
||||||
|
:param number:
|
||||||
|
Integer value
|
||||||
|
:param fill_size:
|
||||||
|
If the optional fill size is given the length of the resulting
|
||||||
|
byte string is expected to be the fill size and will be padded
|
||||||
|
with prefix zero bytes to satisfy that length.
|
||||||
|
:returns:
|
||||||
|
Raw bytes (base-256 representation).
|
||||||
|
:raises:
|
||||||
|
``OverflowError`` when fill_size is given and the number takes up more
|
||||||
|
bytes than fit into the block. This requires the ``overflow``
|
||||||
|
argument to this function to be set to ``False`` otherwise, no
|
||||||
|
error will be raised.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if number < 0:
|
||||||
|
raise ValueError("Number must be an unsigned integer: %d" % number)
|
||||||
|
|
||||||
|
bytes_required = max(1, math.ceil(number.bit_length() / 8))
|
||||||
|
|
||||||
|
if fill_size > 0:
|
||||||
|
return number.to_bytes(fill_size, 'big')
|
||||||
|
|
||||||
|
return number.to_bytes(bytes_required, 'big')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
doctest.testmod()
|
||||||
75
rsa/util.py
Normal file
75
rsa/util.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Utility functions."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
import rsa.key
|
||||||
|
|
||||||
|
|
||||||
|
def private_to_public() -> None:
|
||||||
|
"""Reads a private key and outputs the corresponding public key."""
|
||||||
|
|
||||||
|
# Parse the CLI options
|
||||||
|
parser = OptionParser(usage='usage: %prog [options]',
|
||||||
|
description='Reads a private key and outputs the '
|
||||||
|
'corresponding public key. Both private and public keys use '
|
||||||
|
'the format described in PKCS#1 v1.5')
|
||||||
|
|
||||||
|
parser.add_option('-i', '--input', dest='infilename', type='string',
|
||||||
|
help='Input filename. Reads from stdin if not specified')
|
||||||
|
parser.add_option('-o', '--output', dest='outfilename', type='string',
|
||||||
|
help='Output filename. Writes to stdout of not specified')
|
||||||
|
|
||||||
|
parser.add_option('--inform', dest='inform',
|
||||||
|
help='key format of input - default PEM',
|
||||||
|
choices=('PEM', 'DER'), default='PEM')
|
||||||
|
|
||||||
|
parser.add_option('--outform', dest='outform',
|
||||||
|
help='key format of output - default PEM',
|
||||||
|
choices=('PEM', 'DER'), default='PEM')
|
||||||
|
|
||||||
|
(cli, cli_args) = parser.parse_args(sys.argv)
|
||||||
|
|
||||||
|
# Read the input data
|
||||||
|
if cli.infilename:
|
||||||
|
print('Reading private key from %s in %s format' %
|
||||||
|
(cli.infilename, cli.inform), file=sys.stderr)
|
||||||
|
with open(cli.infilename, 'rb') as infile:
|
||||||
|
in_data = infile.read()
|
||||||
|
else:
|
||||||
|
print('Reading private key from stdin in %s format' % cli.inform,
|
||||||
|
file=sys.stderr)
|
||||||
|
in_data = sys.stdin.read().encode('ascii')
|
||||||
|
|
||||||
|
assert type(in_data) == bytes, type(in_data)
|
||||||
|
|
||||||
|
# Take the public fields and create a public key
|
||||||
|
priv_key = rsa.key.PrivateKey.load_pkcs1(in_data, cli.inform)
|
||||||
|
pub_key = rsa.key.PublicKey(priv_key.n, priv_key.e)
|
||||||
|
|
||||||
|
# Save to the output file
|
||||||
|
out_data = pub_key.save_pkcs1(cli.outform)
|
||||||
|
|
||||||
|
if cli.outfilename:
|
||||||
|
print('Writing public key to %s in %s format' %
|
||||||
|
(cli.outfilename, cli.outform), file=sys.stderr)
|
||||||
|
with open(cli.outfilename, 'wb') as outfile:
|
||||||
|
outfile.write(out_data)
|
||||||
|
else:
|
||||||
|
print('Writing public key to stdout in %s format' % cli.outform,
|
||||||
|
file=sys.stderr)
|
||||||
|
sys.stdout.write(out_data.decode('ascii'))
|
||||||
Loading…
Reference in New Issue
Block a user