Keep aspect ratio for epub output and other changes

This commit is contained in:
RealStickman 2022-09-15 21:21:07 +02:00
parent 6838251d3b
commit e74a0e6ff3
75 changed files with 7586 additions and 13609 deletions

View File

@ -6,7 +6,7 @@ json:{
"epub_inline_toc": false, "epub_inline_toc": false,
"epub_toc_at_end": false, "epub_toc_at_end": false,
"toc_title": null, "toc_title": null,
"preserve_cover_aspect_ratio": false, "preserve_cover_aspect_ratio": true,
"epub_flatten": false, "epub_flatten": false,
"epub_version": "3" "epub_version": "3"
} }

View File

@ -3,7 +3,7 @@
"DeDRMimport_Adobe_Digital_Editions_Key_keys": "/home/marc/Nextcloud/backups", "DeDRMimport_Adobe_Digital_Editions_Key_keys": "/home/marc/Nextcloud/backups",
"Export ADE activation files": "/home/marc/Nextcloud/backups/adobe_account_backup_uuid_2d6cfbec-33fd-43ca-bcf9-e8b281114a17.zip", "Export ADE activation files": "/home/marc/Nextcloud/backups/adobe_account_backup_uuid_2d6cfbec-33fd-43ca-bcf9-e8b281114a17.zip",
"Export ADE keys": "/home/marc/Nextcloud/backups/adobe_uuid_2d6cfbec-33fd-43ca-bcf9-e8b281114a17.der", "Export ADE keys": "/home/marc/Nextcloud/backups/adobe_uuid_2d6cfbec-33fd-43ca-bcf9-e8b281114a17.der",
"add a plugin dialog": "/home/marc/Downloads", "add a plugin dialog": "/home/marc/Downloads/DeDRM_tools_10.0.3",
"add books dialog dir": "/home/marc/Downloads", "add books dialog dir": "/home/marc/Downloads",
"add books dialog dir-last-used-filter-spec-all-files": false, "add books dialog dir-last-used-filter-spec-all-files": false,
"database location dialog": "/home/marc/Nextcloud/Books", "database location dialog": "/home/marc/Nextcloud/Books",
@ -18,7 +18,7 @@
] ]
}, },
"recursive book import root dir dialog": "/home/marc/Nextcloud/Books/Unterhaltung", "recursive book import root dir dialog": "/home/marc/Nextcloud/Books/Unterhaltung",
"save to disk dialog": "/home/marc/Downloads", "save to disk dialog": "/home/marc/Downloads/newBooks",
"sort_history": [ "sort_history": [
[ [
"title", "title",

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{ {
"Plugin Updater plugin:plugin updater dialog": { "Plugin Updater plugin:plugin updater dialog": {
"__class__": "bytearray", "__class__": "bytearray",
"__value__": "AdnQywADAAAAAAHDAAABFQAABdUAAAMKAAABxQAAARcAAAXTAAADCAAAAAAAAAAAB4AAAAHFAAABFwAABdMAAAMI" "__value__": "AdnQywADAAAAAAHHAAABFQAABdkAAAMKAAAByQAAARcAAAXXAAADCAAAAAAAAAAAB4AAAAHJAAABFwAABdcAAAMI"
}, },
"action-layout-toolbar": [ "action-layout-toolbar": [
"Add Books", "Add Books",
@ -80,13 +80,21 @@
"__value__": "AdnQywADAAAAAAAbAAAAEgAAB1gAAAQbAAAAHQAAABQAAAdWAAAEGQAAAAAAAAAAB4AAAAAdAAAAFAAAB1YAAAQZ" "__value__": "AdnQywADAAAAAAAbAAAAEgAAB1gAAAQbAAAAHQAAABQAAAdWAAAEGQAAAAAAAAAAB4AAAAAdAAAAFAAAB1YAAAQZ"
}, },
"bulk_metadata_window_tab": 0, "bulk_metadata_window_tab": 0,
"choose-merge-dialog-geometry": {
"__class__": "bytearray",
"__value__": "AdnQywADAAAAAAKEAAABYgAABPsAAAK9AAAChgAAAWQAAAT5AAACuwAAAAAAAAAAB4AAAAKGAAABZAAABPkAAAK7"
},
"choose-merge-dialog-splitter-state": {
"__class__": "bytearray",
"__value__": "AAAA/wAAAAEAAAACAAABXgAAAQAA/////wEAAAABAA=="
},
"convert_bulk_dialog_geom": { "convert_bulk_dialog_geom": {
"__class__": "bytearray", "__class__": "bytearray",
"__value__": "AdnQywADAAAAAAASAAAAJAAAB08AAAP7AAAAFAAAACYAAAdNAAAD+QAAAAAAAAAAB4AAAAAUAAAAJgAAB00AAAP5" "__value__": "AdnQywADAAAAAAAeAAAAJAAAB1sAAAP7AAAAIAAAACYAAAdZAAAD+QAAAAAAAAAAB4AAAAAgAAAAJgAAB1kAAAP5"
}, },
"convert_single_dialog_geom": { "convert_single_dialog_geom": {
"__class__": "bytearray", "__class__": "bytearray",
"__value__": "AdnQywADAAAAAAAsAAAAEgAAB2kAAAPpAAAALgAAABQAAAdnAAAD5wAAAAAAAAAAB4AAAAAuAAAAFAAAB2cAAAPn" "__value__": "AdnQywADAAAAAAA4AAAAEgAAB3UAAAPpAAAAOgAAABQAAAdzAAAD5wAAAAAAAAAAB4AAAAA6AAAAFAAAB3MAAAPn"
}, },
"cover_browser_splitter_vertical_state": [ "cover_browser_splitter_vertical_state": [
false, false,
@ -196,7 +204,7 @@
}, },
"duplicates-question-dialog-geometry": { "duplicates-question-dialog-geometry": {
"__class__": "bytearray", "__class__": "bytearray",
"__value__": "AdnQywADAAAAAACGAAABTAAAA2AAAALPAAAAiAAAAU4AAANeAAACzQAAAAAAAAAAB4AAAACIAAABTgAAA14AAALN" "__value__": "AdnQywADAAAAAACOAAABTAAAA2gAAALPAAAAkAAAAU4AAANmAAACzQAAAAAAAAAAB4AAAACQAAABTgAAA2YAAALN"
}, },
"grid view visible": false, "grid view visible": false,
"jobs view column layout3": { "jobs view column layout3": {
@ -208,7 +216,7 @@
"__value__": "AdnQywADAAAAAAGYAAABEQAABQ4AAAMyAAABmgAAARMAAAUMAAADMAAAAAAAAAAAB4AAAAGaAAABEwAABQwAAAMw" "__value__": "AdnQywADAAAAAAGYAAABEQAABQ4AAAMyAAABmgAAARMAAAUMAAADMAAAAAAAAAAAB4AAAAGaAAABEwAABQwAAAMw"
}, },
"library_usage_stats": { "library_usage_stats": {
"/home/marc/Calibre-Bibliothek": 184 "/home/marc/Calibre-Bibliothek": 200
}, },
"metadata-download-identify-widget-splitter-state": { "metadata-download-identify-widget-splitter-state": {
"__class__": "bytearray", "__class__": "bytearray",
@ -220,15 +228,15 @@
}, },
"metasingle_window_geometry3": { "metasingle_window_geometry3": {
"__class__": "bytearray", "__class__": "bytearray",
"__value__": "AdnQywADAAAAAAA8AAAAEgAAB3kAAAQbAAAAPgAAABQAAAd3AAAEGQAAAAAAAAAAB4AAAAA+AAAAFAAAB3cAAAQZ" "__value__": "AdnQywADAAAAAAA+AAAAEgAAB3sAAAQbAAAAQAAAABQAAAd5AAAEGQAAAAAAAAAAB4AAAABAAAAAFAAAB3kAAAQZ"
}, },
"plugin config dialog:Dateityp:DeACSM": { "plugin config dialog:Dateityp:DeACSM": {
"__class__": "bytearray", "__class__": "bytearray",
"__value__": "AdnQywADAAAAAAMiAAABRAAABKAAAAL9AAADJAAAAUYAAASeAAAC+wAAAAAAAAAAB4AAAAMkAAABRgAABJ4AAAL7" "__value__": "AdnQywADAAAAAAMkAAABRAAABKIAAAL9AAADJgAAAUYAAASgAAAC+wAAAAAAAAAAB4AAAAMmAAABRgAABKAAAAL7"
}, },
"plugin config dialog:Dateityp:DeDRM": { "plugin config dialog:Dateityp:DeDRM": {
"__class__": "bytearray", "__class__": "bytearray",
"__value__": "AdnQywADAAAAAAP8AAAApAAABRYAAAKaAAAD/gAAAKYAAAUUAAACmAAAAAAAAAAAB4AAAAP+AAAApgAABRQAAAKY" "__value__": "AdnQywADAAAAAAQAAAAApAAABRoAAAKaAAAEAgAAAKYAAAUYAAACmAAAAAAAAAAAB4AAAAQCAAAApgAABRgAAAKY"
}, },
"preferences dialog geometry": { "preferences dialog geometry": {
"__class__": "bytearray", "__class__": "bytearray",

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Will Bond <will@wbond.net> Copyright (c) 2015-2022 Will Bond <will@wbond.net>
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in this software and associated documentation files (the "Software"), to deal in

View File

@ -0,0 +1,305 @@
Metadata-Version: 2.1
Name: asn1crypto
Version: 1.5.1
Summary: Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP
Home-page: https://github.com/wbond/asn1crypto
Author: wbond
Author-email: will@wbond.net
License: MIT
Description: # asn1crypto
A fast, pure Python library for parsing and serializing ASN.1 structures.
- [Features](#features)
- [Why Another Python ASN.1 Library?](#why-another-python-asn1-library)
- [Related Crypto Libraries](#related-crypto-libraries)
- [Current Release](#current-release)
- [Dependencies](#dependencies)
- [Installation](#installation)
- [License](#license)
- [Security Policy](#security-policy)
- [Documentation](#documentation)
- [Continuous Integration](#continuous-integration)
- [Testing](#testing)
- [Development](#development)
- [CI Tasks](#ci-tasks)
[![GitHub Actions CI](https://github.com/wbond/asn1crypto/workflows/CI/badge.svg)](https://github.com/wbond/asn1crypto/actions?workflow=CI)
[![CircleCI](https://circleci.com/gh/wbond/asn1crypto.svg?style=shield)](https://circleci.com/gh/wbond/asn1crypto)
[![PyPI](https://img.shields.io/pypi/v/asn1crypto.svg)](https://pypi.org/project/asn1crypto/)
## Features
In addition to an ASN.1 BER/DER decoder and DER serializer, the project includes
a bunch of ASN.1 structures for use with various common cryptography standards:
| Standard | Module | Source |
| ---------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| X.509 | [`asn1crypto.x509`](asn1crypto/x509.py) | [RFC 5280](https://tools.ietf.org/html/rfc5280) |
| CRL | [`asn1crypto.crl`](asn1crypto/crl.py) | [RFC 5280](https://tools.ietf.org/html/rfc5280) |
| CSR | [`asn1crypto.csr`](asn1crypto/csr.py) | [RFC 2986](https://tools.ietf.org/html/rfc2986), [RFC 2985](https://tools.ietf.org/html/rfc2985) |
| OCSP | [`asn1crypto.ocsp`](asn1crypto/ocsp.py) | [RFC 6960](https://tools.ietf.org/html/rfc6960) |
| PKCS#12 | [`asn1crypto.pkcs12`](asn1crypto/pkcs12.py) | [RFC 7292](https://tools.ietf.org/html/rfc7292) |
| PKCS#8 | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 5208](https://tools.ietf.org/html/rfc5208) |
| PKCS#1 v2.1 (RSA keys) | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 3447](https://tools.ietf.org/html/rfc3447) |
| DSA keys | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 3279](https://tools.ietf.org/html/rfc3279) |
| Elliptic curve keys | [`asn1crypto.keys`](asn1crypto/keys.py) | [SECG SEC1 V2](http://www.secg.org/sec1-v2.pdf) |
| PKCS#3 v1.4 | [`asn1crypto.algos`](asn1crypto/algos.py) | [PKCS#3 v1.4](ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-3.asc) |
| PKCS#5 v2.1 | [`asn1crypto.algos`](asn1crypto/algos.py) | [PKCS#5 v2.1](http://www.emc.com/collateral/white-papers/h11302-pkcs5v2-1-password-based-cryptography-standard-wp.pdf) |
| CMS (and PKCS#7) | [`asn1crypto.cms`](asn1crypto/cms.py) | [RFC 5652](https://tools.ietf.org/html/rfc5652), [RFC 2315](https://tools.ietf.org/html/rfc2315) |
| TSP | [`asn1crypto.tsp`](asn1crypto/tsp.py) | [RFC 3161](https://tools.ietf.org/html/rfc3161) |
| PDF signatures | [`asn1crypto.pdf`](asn1crypto/pdf.py) | [PDF 1.7](http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf) |
## Why Another Python ASN.1 Library?
Python has long had the [pyasn1](https://pypi.org/project/pyasn1/) and
[pyasn1_modules](https://pypi.org/project/pyasn1-modules/) available for
parsing and serializing ASN.1 structures. While the project does include a
comprehensive set of tools for parsing and serializing, the performance of the
library can be very poor, especially when dealing with bit fields and parsing
large structures such as CRLs.
After spending extensive time using *pyasn1*, the following issues were
identified:
1. Poor performance
2. Verbose, non-pythonic API
3. Out-dated and incomplete definitions in *pyasn1-modules*
4. No simple way to map data to native Python data structures
5. No mechanism for overridden universal ASN.1 types
The *pyasn1* API is largely method driven, and uses extensive configuration
objects and lowerCamelCase names. There were no consistent options for
converting types of native Python data structures. Since the project supports
out-dated versions of Python, many newer language features are unavailable
for use.
Time was spent trying to profile issues with the performance, however the
architecture made it hard to pin down the primary source of the poor
performance. Attempts were made to improve performance by utilizing unreleased
patches and delaying parsing using the `Any` type. Even with such changes, the
performance was still unacceptably slow.
Finally, a number of structures in the cryptographic space use universal data
types such as `BitString` and `OctetString`, but interpret the data as other
types. For instance, signatures are really byte strings, but are encoded as
`BitString`. Elliptic curve keys use both `BitString` and `OctetString` to
represent integers. Parsing these structures as the base universal types and
then re-interpreting them wastes computation.
*asn1crypto* uses the following techniques to improve performance, especially
when extracting one or two fields from large, complex structures:
- Delayed parsing of byte string values
- Persistence of original ASN.1 encoded data until a value is changed
- Lazy loading of child fields
- Utilization of high-level Python stdlib modules
While there is no extensive performance test suite, the
`CRLTests.test_parse_crl` test case was used to parse a 21MB CRL file on a
late 2013 rMBP. *asn1crypto* parsed the certificate serial numbers in just
under 8 seconds. With *pyasn1*, using definitions from *pyasn1-modules*, the
same parsing took over 4,100 seconds.
For smaller structures the performance difference can range from a few times
faster to an order of magnitude or more.
## Related Crypto Libraries
*asn1crypto* is part of the modularcrypto family of Python packages:
- [asn1crypto](https://github.com/wbond/asn1crypto)
- [oscrypto](https://github.com/wbond/oscrypto)
- [csrbuilder](https://github.com/wbond/csrbuilder)
- [certbuilder](https://github.com/wbond/certbuilder)
- [crlbuilder](https://github.com/wbond/crlbuilder)
- [ocspbuilder](https://github.com/wbond/ocspbuilder)
- [certvalidator](https://github.com/wbond/certvalidator)
## Current Release
1.5.0 - [changelog](changelog.md)
## Dependencies
Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 or pypy. *No third-party
packages required.*
## Installation
```bash
pip install asn1crypto
```
## License
*asn1crypto* is licensed under the terms of the MIT license. See the
[LICENSE](LICENSE) file for the exact license text.
## Security Policy
The security policies for this project are covered in
[SECURITY.md](https://github.com/wbond/asn1crypto/blob/master/SECURITY.md).
## Documentation
The documentation for *asn1crypto* is composed of tutorials on basic usage and
links to the source for the various pre-defined type classes.
### Tutorials
- [Universal Types with BER/DER Decoder and DER Encoder](docs/universal_types.md)
- [PEM Encoder and Decoder](docs/pem.md)
### Reference
- [Universal types](asn1crypto/core.py), `asn1crypto.core`
- [Digest, HMAC, signed digest and encryption algorithms](asn1crypto/algos.py), `asn1crypto.algos`
- [Private and public keys](asn1crypto/keys.py), `asn1crypto.keys`
- [X509 certificates](asn1crypto/x509.py), `asn1crypto.x509`
- [Certificate revocation lists (CRLs)](asn1crypto/crl.py), `asn1crypto.crl`
- [Online certificate status protocol (OCSP)](asn1crypto/ocsp.py), `asn1crypto.ocsp`
- [Certificate signing requests (CSRs)](asn1crypto/csr.py), `asn1crypto.csr`
- [Private key/certificate containers (PKCS#12)](asn1crypto/pkcs12.py), `asn1crypto.pkcs12`
- [Cryptographic message syntax (CMS, PKCS#7)](asn1crypto/cms.py), `asn1crypto.cms`
- [Time stamp protocol (TSP)](asn1crypto/tsp.py), `asn1crypto.tsp`
- [PDF signatures](asn1crypto/pdf.py), `asn1crypto.pdf`
## Continuous Integration
Various combinations of platforms and versions of Python are tested via:
- [macOS, Linux, Windows](https://github.com/wbond/asn1crypto/actions/workflows/ci.yml) via GitHub Actions
- [arm64](https://circleci.com/gh/wbond/asn1crypto) via CircleCI
## Testing
Tests are written using `unittest` and require no third-party packages.
Depending on what type of source is available for the package, the following
commands can be used to run the test suite.
### Git Repository
When working within a Git working copy, or an archive of the Git repository,
the full test suite is run via:
```bash
python run.py tests
```
To run only some tests, pass a regular expression as a parameter to `tests`.
```bash
python run.py tests ocsp
```
### PyPi Source Distribution
When working within an extracted source distribution (aka `.tar.gz`) from
PyPi, the full test suite is run via:
```bash
python setup.py test
```
### Package
When the package has been installed via pip (or another method), the package
`asn1crypto_tests` may be installed and invoked to run the full test suite:
```bash
pip install asn1crypto_tests
python -m asn1crypto_tests
```
## Development
To install the package used for linting, execute:
```bash
pip install --user -r requires/lint
```
The following command will run the linter:
```bash
python run.py lint
```
Support for code coverage can be installed via:
```bash
pip install --user -r requires/coverage
```
Coverage is measured by running:
```bash
python run.py coverage
```
To change the version number of the package, run:
```bash
python run.py version {pep440_version}
```
To install the necessary packages for releasing a new version on PyPI, run:
```bash
pip install --user -r requires/release
```
Releases are created by:
- Making a git tag in [PEP 440](https://www.python.org/dev/peps/pep-0440/#examples-of-compliant-version-schemes) format
- Running the command:
```bash
python run.py release
```
Existing releases can be found at https://pypi.org/project/asn1crypto/.
## CI Tasks
A task named `deps` exists to download and stage all necessary testing
dependencies. On posix platforms, `curl` is used for downloads and on Windows
PowerShell with `Net.WebClient` is used. This configuration sidesteps issues
related to getting pip to work properly and messing with `site-packages` for
the version of Python being used.
The `ci` task runs `lint` (if flake8 is available for the version of Python) and
`coverage` (or `tests` if coverage is not available for the version of Python).
If the current directory is a clean git working copy, the coverage data is
submitted to codecov.io.
```bash
python run.py deps
python run.py ci
```
Keywords: asn1 crypto pki x509 certificate rsa dsa ec dh
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
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: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Security :: Cryptography
Description-Content-Type: text/markdown

View File

@ -0,0 +1,37 @@
# Security Policy
## How to Report
If you believe you've found an issue that has security implications, please do
not post a public issue on GitHub. Instead, email the project lead, Will Bond,
at will@wbond.net.
You should receive a response within two business days, and follow up emails
during the process of confirming the potential issue.
## Supported Versions
The asn1crypto project only provides security patches for the most recent
release. This is primarily a function of available resources.
## Disclosure Process
The following process is used when handling a potential secuirty issue:
1. The report should be emailed to will@wbond.net, and NOT posted on the
GitHub issue tracker.
2. Confirmation of receipt of the report should happen within two business
days.
3. Information will be collected and an investigation will be performed to
determine if a security issue exists.
4. If no security issue is found, the process will end.
5. A fix for the issue and announcement will be drafted.
6. A release schedule and accouncement will be negotiated between the
reporter and the project
7. The security contacts for Arch Linux, Conda, Debian, Fedora, FreeBSD,
Ubuntu, and Tidelift will be contacted to notify them of an upcoming
security release.
8. Fixes for all vulnerabilities will be performed, and new releases made,
but without mention of a security issue. These changes and releases will
be published before the announcement.
9. An announcement will be made disclosing the vulnerability and the fix.

View File

@ -30,6 +30,7 @@ from .algos import (
_ForceNullParameters, _ForceNullParameters,
DigestAlgorithm, DigestAlgorithm,
EncryptionAlgorithm, EncryptionAlgorithm,
EncryptionAlgorithmId,
HmacAlgorithm, HmacAlgorithm,
KdfAlgorithm, KdfAlgorithm,
RSAESOAEPParams, RSAESOAEPParams,
@ -100,6 +101,8 @@ class CMSAttributeType(ObjectIdentifier):
'1.2.840.113549.1.9.4': 'message_digest', '1.2.840.113549.1.9.4': 'message_digest',
'1.2.840.113549.1.9.5': 'signing_time', '1.2.840.113549.1.9.5': 'signing_time',
'1.2.840.113549.1.9.6': 'counter_signature', '1.2.840.113549.1.9.6': 'counter_signature',
# https://datatracker.ietf.org/doc/html/rfc2633#section-2.5.2
'1.2.840.113549.1.9.15': 'smime_capabilities',
# https://tools.ietf.org/html/rfc2633#page-26 # https://tools.ietf.org/html/rfc2633#page-26
'1.2.840.113549.1.9.16.2.11': 'encrypt_key_pref', '1.2.840.113549.1.9.16.2.11': 'encrypt_key_pref',
# https://tools.ietf.org/html/rfc3161#page-20 # https://tools.ietf.org/html/rfc3161#page-20
@ -273,7 +276,7 @@ class V2Form(Sequence):
class AttCertIssuer(Choice): class AttCertIssuer(Choice):
_alternatives = [ _alternatives = [
('v1_form', GeneralNames), ('v1_form', GeneralNames),
('v2_form', V2Form, {'explicit': 0}), ('v2_form', V2Form, {'implicit': 0}),
] ]
@ -315,7 +318,7 @@ class SetOfSvceAuthInfo(SetOf):
class RoleSyntax(Sequence): class RoleSyntax(Sequence):
_fields = [ _fields = [
('role_authority', GeneralNames, {'implicit': 0, 'optional': True}), ('role_authority', GeneralNames, {'implicit': 0, 'optional': True}),
('role_name', GeneralName, {'implicit': 1}), ('role_name', GeneralName, {'explicit': 1}),
] ]
@ -337,7 +340,7 @@ class ClassList(BitString):
class SecurityCategory(Sequence): class SecurityCategory(Sequence):
_fields = [ _fields = [
('type', ObjectIdentifier, {'implicit': 0}), ('type', ObjectIdentifier, {'implicit': 0}),
('value', Any, {'implicit': 1}), ('value', Any, {'explicit': 1}),
] ]
@ -347,9 +350,9 @@ class SetOfSecurityCategory(SetOf):
class Clearance(Sequence): class Clearance(Sequence):
_fields = [ _fields = [
('policy_id', ObjectIdentifier, {'implicit': 0}), ('policy_id', ObjectIdentifier),
('class_list', ClassList, {'implicit': 1, 'default': 'unclassified'}), ('class_list', ClassList, {'default': set(['unclassified'])}),
('security_categories', SetOfSecurityCategory, {'implicit': 2, 'optional': True}), ('security_categories', SetOfSecurityCategory, {'optional': True}),
] ]
@ -946,6 +949,21 @@ class SMIMEEncryptionKeyPreferences(SetOf):
_child_spec = SMIMEEncryptionKeyPreference _child_spec = SMIMEEncryptionKeyPreference
class SMIMECapabilityIdentifier(Sequence):
_fields = [
('capability_id', EncryptionAlgorithmId),
('parameters', Any, {'optional': True}),
]
class SMIMECapabilites(SequenceOf):
_child_spec = SMIMECapabilityIdentifier
class SetOfSMIMECapabilites(SetOf):
_child_spec = SMIMECapabilites
ContentInfo._oid_specs = { ContentInfo._oid_specs = {
'data': OctetString, 'data': OctetString,
'signed_data': SignedData, 'signed_data': SignedData,
@ -981,4 +999,5 @@ CMSAttribute._oid_specs = {
'microsoft_nested_signature': SetOfContentInfo, 'microsoft_nested_signature': SetOfContentInfo,
'microsoft_time_stamp_token': SetOfContentInfo, 'microsoft_time_stamp_token': SetOfContentInfo,
'encrypt_key_pref': SMIMEEncryptionKeyPreferences, 'encrypt_key_pref': SMIMEEncryptionKeyPreferences,
'smime_capabilities': SetOfSMIMECapabilites,
} }

View File

@ -4113,6 +4113,10 @@ class Sequence(Asn1Value):
if self._header is not None and self._header[-1:] == b'\x80': if self._header is not None and self._header[-1:] == b'\x80':
force = True force = True
# We can't force encoding if we don't have a spec
if force and self._fields == [] and self.__class__ is Sequence:
force = False
if force: if force:
self._set_contents(force=force) self._set_contents(force=force)

View File

@ -752,7 +752,7 @@ class PrivateKeyInfo(Sequence):
type_name(private_key) type_name(private_key)
)) ))
if algorithm == 'rsa': if algorithm == 'rsa' or algorithm == 'rsassa_pss':
if not isinstance(private_key, RSAPrivateKey): if not isinstance(private_key, RSAPrivateKey):
private_key = RSAPrivateKey.load(private_key) private_key = RSAPrivateKey.load(private_key)
params = Null() params = Null()
@ -893,7 +893,7 @@ class PrivateKeyInfo(Sequence):
def algorithm(self): def algorithm(self):
""" """
:return: :return:
A unicode string of "rsa", "dsa" or "ec" A unicode string of "rsa", "rsassa_pss", "dsa" or "ec"
""" """
if self._algorithm is None: if self._algorithm is None:
@ -908,7 +908,7 @@ class PrivateKeyInfo(Sequence):
""" """
if self._bit_size is None: if self._bit_size is None:
if self.algorithm == 'rsa': if self.algorithm == 'rsa' or self.algorithm == 'rsassa_pss':
prime = self['private_key'].parsed['modulus'].native prime = self['private_key'].parsed['modulus'].native
elif self.algorithm == 'dsa': elif self.algorithm == 'dsa':
prime = self['private_key_algorithm']['parameters']['p'].native prime = self['private_key_algorithm']['parameters']['p'].native
@ -1120,7 +1120,7 @@ class PublicKeyInfo(Sequence):
type_name(public_key) type_name(public_key)
)) ))
if algorithm != 'rsa': if algorithm != 'rsa' and algorithm != 'rsassa_pss':
raise ValueError(unwrap( raise ValueError(unwrap(
''' '''
algorithm must "rsa", not %s algorithm must "rsa", not %s
@ -1222,7 +1222,7 @@ class PublicKeyInfo(Sequence):
def algorithm(self): def algorithm(self):
""" """
:return: :return:
A unicode string of "rsa", "dsa" or "ec" A unicode string of "rsa", "rsassa_pss", "dsa" or "ec"
""" """
if self._algorithm is None: if self._algorithm is None:
@ -1240,7 +1240,7 @@ class PublicKeyInfo(Sequence):
if self.algorithm == 'ec': if self.algorithm == 'ec':
self._bit_size = int(((len(self['public_key'].native) - 1) / 2) * 8) self._bit_size = int(((len(self['public_key'].native) - 1) / 2) * 8)
else: else:
if self.algorithm == 'rsa': if self.algorithm == 'rsa' or self.algorithm == 'rsassa_pss':
prime = self['public_key'].parsed['modulus'].native prime = self['public_key'].parsed['modulus'].native
elif self.algorithm == 'dsa': elif self.algorithm == 'dsa':
prime = self['algorithm']['parameters']['p'].native prime = self['algorithm']['parameters']['p'].native

View File

@ -2,5 +2,5 @@
from __future__ import unicode_literals, division, absolute_import, print_function from __future__ import unicode_literals, division, absolute_import, print_function
__version__ = '1.4.0' __version__ = '1.5.1'
__version_info__ = (1, 4, 0) __version_info__ = (1, 5, 1)

View File

@ -1,5 +1,43 @@
# changelog # changelog
## 1.5.1
- Handle RSASSA-PSS in `keys.PrivateKeyInfo.bit_size` and
`keys.PublicKeyInfo.bit_size`
- Handle RSASSA-PSS in `keys.PrivateKeyInfo.wrap` and
`keys.PublicKeyInfo.wrap`
- Updated docs for `keys.PrivateKeyInfo.algorithm` and
`keys.PublicKeyInfo.algorithm` to reflect that they can return
`"rsassa_pss"`
## 1.5.0
- Fix `tsp.TimeStampAndCRL` to be a `core.Sequence` instead of a
`core.SequenceOf` *via @joernheissler*
- Added OIDs for Edwards curves from RFC 8410 - via @MatthiasValvekens
- Fixed convenience attributes on `algos.EncryptionAlgorithm` when the
algorithm is RC2 *via @joernheissler*
- Added Microsoft OIDs `microsoft_enrollment_csp_provider`
(`1.3.6.1.4.1.311.13.2.2`), `microsoft_os_version`
(`1.3.6.1.4.1.311.13.2.3`) and `microsoft_request_client_info`
(`1.3.6.1.4.1.311.21.20`)
to `csr.CSRAttributeType` along with supporting extension structures
*via @qha*
- Added Microsoft OID `microsoft_enroll_certtype` (`1.3.6.1.4.1.311.20.2`)
to `x509.ExtensionId` *via @qha*
- Fixed a few bugs with parsing indefinite-length encodings *via @davidben*
- Added various bounds checks to parsing engine *via @davidben*
- Fixed a bug with tags not always being minimally encoded *via @davidben*
- Fixed `cms.RoleSyntax`, `cms.SecurityCategory` and `cms.AttCertIssuer` to
have explicit instead of implicit tagging *via @MatthiasValvekens*
- Fixed tagging of, and default value for fields in `cms.Clearance` *via
@MatthiasValvekens*
- Fixed calling `.dump(force=True)` when the value has undefined/unknown
`core.Sequence` fields. Previously the value would be truncated, now
the existing encoding is preserved.
- Added sMIME capabilities (`1.2.840.113549.1.9.15`) support from RFC 2633
to `cms.CMSAttribute` *via Hellzed*
## 1.4.0 ## 1.4.0
- `core.ObjectIdentifier` and all derived classes now obey X.660 §7.6 and - `core.ObjectIdentifier` and all derived classes now obey X.660 §7.6 and

View File

@ -0,0 +1,79 @@
# PEM Decoder and Encoder
Often times DER-encoded data is wrapped in PEM encoding. This allows the binary
DER data to be identified and reliably sent over various communication channels.
The `asn1crypto.pem` module includes three functions:
- `detect(byte_string)`
- `unarmor(pem_bytes, multiple=False)`
- `armor(type_name, der_bytes, headers=None)`
## detect()
The `detect()` function accepts a byte string and looks for a `BEGIN` block
line. This is useful to determine in a byte string needs to be PEM-decoded
before parsing.
```python
from asn1crypto import pem, x509
with open('/path/to/cert', 'rb') as f:
der_bytes = f.read()
if pem.detect(der_bytes):
_, _, der_bytes = pem.unarmor(der_bytes)
```
## unarmor()
The `unarmor()` function accepts a byte string and the flag to indicates if
more than one PEM block may be contained in the byte string. The result is
a three-element tuple.
- The first element is a unicode string of the type of PEM block. Examples
include: `CERTIFICATE`, `PRIVATE KEY`, `PUBLIC KEY`.
- The second element is a `dict` of PEM block headers. Headers are typically
only used by encrypted OpenSSL private keys, and are in the format
`Name: Value`.
- The third element is a byte string of the decoded block contents.
```python
from asn1crypto import pem, x509
with open('/path/to/cert', 'rb') as f:
der_bytes = f.read()
if pem.detect(der_bytes):
type_name, headers, der_bytes = pem.unarmor(der_bytes)
cert = x509.Certificate.load(der_bytes)
```
If the `multiple` keyword argument is set to `True`, a generator will be
returned.
```python
from asn1crypto import pem, x509
certs = []
with open('/path/to/ca_certs', 'rb') as f:
for type_name, headers, der_bytes in pem.unarmor(f.read(), multiple=True):
certs.append(x509.Certificate.load(der_bytes))
```
## armor()
The `armor()` function accepts three parameters: a unicode string of the block
type name, a byte string to encode and an optional keyword argument `headers`,
that should be a `dict` of headers to add after the `BEGIN` line. Headers are
typically only used by encrypted OpenSSL private keys.
```python
from asn1crypto import pem, x509
# cert is an instance of x509.Certificate
with open('/path/to/cert', 'wb') as f:
der_bytes = cert.dump()
pem_bytes = pem.armor('CERTIFICATE', der_bytes)
f.write(pem_bytes)
```

View File

@ -0,0 +1,23 @@
# asn1crypto Documentation
The documentation for *asn1crypto* is composed of tutorials on basic usage and
links to the source for the various pre-defined type classes.
## Tutorials
- [Universal Types with BER/DER Decoder and DER Encoder](universal_types.md)
- [PEM Decoder and Encoder](pem.md)
## Reference
- [Universal types](../asn1crypto/core.py), `asn1crypto.core`
- [Digest, HMAC, signed digest and encryption algorithms](../asn1crypto/algos.py), `asn1crypto.algos`
- [Private and public keys](../asn1crypto/keys.py), `asn1crypto.keys`
- [X.509 certificates](../asn1crypto/x509.py), `asn1crypto.x509`
- [Certificate revocation lists (CRLs)](../asn1crypto/crl.py), `asn1crypto.crl`
- [Online certificate status protocol (OCSP)](../asn1crypto/ocsp.py), `asn1crypto.ocsp`
- [Certificate signing requests (CSRs)](../asn1crypto/csr.py), `asn1crypto.csr`
- [Private key/certificate containers (PKCS#12)](../asn1crypto/pkcs12.py), `asn1crypto.pkcs12`
- [Cryptographic message syntax (CMS, PKCS#7)](../asn1crypto/cms.py), `asn1crypto.cms`
- [Time stamp protocol (TSP)](../asn1crypto/tsp.py), `asn1crypto.tsp`
- [PDF signatures](../asn1crypto/pdf.py), `asn1crypto.pdf`

View File

@ -0,0 +1,675 @@
# Universal Types with BER/DER Decoder and DER Encoder
The *asn1crypto* library is a combination of universal type classes that
implement BER/DER decoding and DER encoding, a PEM encoder and decoder, and a
number of pre-built cryptographic type classes. This document covers the
universal type classes.
For a general overview of ASN.1 as used in cryptography, please see
[A Layman's Guide to a Subset of ASN.1, BER, and DER](http://luca.ntop.org/Teaching/Appunti/asn1.html).
This page contains the following sections:
- [Universal Types](#universal-types)
- [Basic Usage](#basic-usage)
- [Sequence](#sequence)
- [Set](#set)
- [SequenceOf](#sequenceof)
- [SetOf](#setof)
- [Integer](#integer)
- [Enumerated](#enumerated)
- [ObjectIdentifier](#objectidentifier)
- [BitString](#bitstring)
- [Strings](#strings)
- [UTCTime](#utctime)
- [GeneralizedTime](#generalizedtime)
- [Choice](#choice)
- [Any](#any)
- [Specification via OID](#specification-via-oid)
- [Explicit and Implicit Tagging](#explicit-and-implicit-tagging)
## Universal Types
For general purpose ASN.1 parsing, the `asn1crypto.core` module is used. It
contains the following classes, that parse, represent and serialize all of the
ASN.1 universal types:
| Class | Native Type | Implementation Notes |
| ------------------ | -------------------------------------- | ------------------------------------ |
| `Boolean` | `bool` | |
| `Integer` | `int` | may be `long` on Python 2 |
| `BitString` | `tuple` of `int` or `set` of `unicode` | `set` used if `_map` present |
| `OctetString` | `bytes` (`str`) | |
| `Null` | `None` | |
| `ObjectIdentifier` | `str` (`unicode`) | string is dotted integer format |
| `ObjectDescriptor` | | no native conversion |
| `InstanceOf` | | no native conversion |
| `Real` | | no native conversion |
| `Enumerated` | `str` (`unicode`) | `_map` must be set |
| `UTF8String` | `str` (`unicode`) | |
| `RelativeOid` | `str` (`unicode`) | string is dotted integer format |
| `Sequence` | `OrderedDict` | |
| `SequenceOf` | `list` | |
| `Set` | `OrderedDict` | |
| `SetOf` | `list` | |
| `EmbeddedPdv` | `OrderedDict` | no named field parsing |
| `NumericString` | `str` (`unicode`) | no charset limitations |
| `PrintableString` | `str` (`unicode`) | no charset limitations |
| `TeletexString` | `str` (`unicode`) | |
| `VideotexString` | `bytes` (`str`) | no unicode conversion |
| `IA5String` | `str` (`unicode`) | |
| `UTCTime` | `datetime.datetime` | |
| `GeneralizedTime` | `datetime.datetime` | treated as UTC when no timezone |
| `GraphicString` | `str` (`unicode`) | unicode conversion as latin1 |
| `VisibleString` | `str` (`unicode`) | no charset limitations |
| `GeneralString` | `str` (`unicode`) | unicode conversion as latin1 |
| `UniversalString` | `str` (`unicode`) | |
| `CharacterString` | `str` (`unicode`) | unicode conversion as latin1 |
| `BMPString` | `str` (`unicode`) | |
For *Native Type*, the Python 3 type is listed first, with the Python 2 type
in parentheses.
As mentioned next to some of the types, value parsing may not be implemented
for types not currently used in cryptography (such as `ObjectDescriptor`,
`InstanceOf` and `Real`). Additionally some of the string classes don't
enforce character set limitations, and for some string types that accept all
different encodings, the default encoding is set to latin1.
In addition, there are a few overridden types where various specifications use
a `BitString` or `OctetString` type to represent a different type. These
include:
| Class | Native Type | Implementation Notes |
| -------------------- | ------------------- | ------------------------------- |
| `OctetBitString` | `bytes` (`str`) | |
| `IntegerBitString` | `int` | may be `long` on Python 2 |
| `IntegerOctetString` | `int` | may be `long` on Python 2 |
For situations where the DER encoded bytes from one type is embedded in another,
the `ParsableOctetString` and `ParsableOctetBitString` classes exist. These
function the same as `OctetString` and `OctetBitString`, however they also
have an attribute `.parsed` and a method `.parse()` that allows for
parsing the content as ASN.1 structures.
All of these overrides can be used with the `cast()` method to convert between
them. The only requirement is that the class being casted to has the same tag
as the original class. No re-encoding is done, rather the contents are simply
re-interpreted.
```python
from asn1crypto.core import BitString, OctetBitString, IntegerBitString
bit = BitString((
0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 1, 0,
))
# Will print (0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0)
print(bit.native)
octet = bit.cast(OctetBitString)
# Will print b'\x01\x02'
print(octet.native)
i = bit.cast(IntegerBitString)
# Will print 258
print(i.native)
```
## Basic Usage
All of the universal types implement four methods, a class method `.load()` and
the instance methods `.dump()`, `.copy()` and `.debug()`.
`.load()` accepts a byte string of DER or BER encoded data and returns an
object of the class it was called on. `.dump()` returns the serialization of
an object into DER encoding.
```python
from asn1crypto.core import Sequence
parsed = Sequence.load(der_byte_string)
serialized = parsed.dump()
```
By default, *asn1crypto* tries to be efficient and caches serialized data for
better performance. If the input data is possibly BER encoded, but the output
must be DER encoded, the `force` parameter may be used with `.dump()`.
```python
from asn1crypto.core import Sequence
parsed = Sequence.load(der_byte_string)
der_serialized = parsed.dump(force=True)
```
The `.copy()` method creates a deep copy of an object, allowing child fields to
be modified without affecting the original.
```python
from asn1crypto.core import Sequence
seq1 = Sequence.load(der_byte_string)
seq2 = seq1.copy()
seq2[0] = seq1[0] + 1
if seq1[0] != seq2[0]:
print('Copies have distinct contents')
```
The `.debug()` method is available to help in situations where interaction with
another ASN.1 serializer or parsing is not functioning as expected. Calling
this method will print a tree structure with information about the header bytes,
class, method, tag, special tagging, content bytes, native Python value, child
fields and any sub-parsed values.
```python
from asn1crypto.core import Sequence
parsed = Sequence.load(der_byte_string)
parsed.debug()
```
In addition to the available methods, every instance has a `.native` property
that converts the data into a native Python data type.
```python
import pprint
from asn1crypto.core import Sequence
parsed = Sequence.load(der_byte_string)
pprint(parsed.native)
```
## Sequence
One of the core structures when dealing with ASN.1 is the Sequence type. The
`Sequence` class can handle field with universal data types, however in most
situations the `_fields` property will need to be set with the expected
definition of each field in the Sequence.
### Configuration
The `_fields` property must be set to a `list` of 2-3 element `tuple`s. The
first element in the tuple must be a unicode string of the field name. The
second must be a type class - either a universal type, or a custom type. The
third, and optional, element is a `dict` with parameters to pass to the type
class for things like default values, marking the field as optional, or
implicit/explicit tagging.
```python
from asn1crypto.core import Sequence, Integer, OctetString, IA5String
class MySequence(Sequence):
_fields = [
('field_one', Integer),
('field_two', OctetString),
('field_three', IA5String, {'optional': True}),
]
```
Implicit and explicit tagging will be covered in more detail later, however
the following are options that can be set for each field type class:
- `{'default: 1}` sets the field's default value to `1`, allowing it to be
omitted from the serialized form
- `{'optional': True}` set the field to be optional, allowing it to be
omitted
### Usage
To access values of the sequence, use dict-like access via `[]` and use the
name of the field:
```python
seq = MySequence.load(der_byte_string)
print(seq['field_two'].native)
```
The values of fields can be set by assigning via `[]`. If the value assigned is
of the correct type class, it will be used as-is. If the value is not of the
correct type class, a new instance of that type class will be created and the
value will be passed to the constructor.
```python
seq = MySequence.load(der_byte_string)
# These statements will result in the same state
seq['field_one'] = Integer(5)
seq['field_one'] = 5
```
When fields are complex types such as `Sequence` or `SequenceOf`, there is no
way to construct the value out of a native Python data type.
### Optional Fields
When a field is configured via the `optional` parameter, not present in the
`Sequence`, but accessed, the `VOID` object will be returned. This is an object
that is serialized to an empty byte string and returns `None` when `.native` is
accessed.
## Set
The `Set` class is configured in the same was as `Sequence`, however it allows
serialized fields to be in any order, per the ASN.1 standard.
```python
from asn1crypto.core import Set, Integer, OctetString, IA5String
class MySet(Set):
_fields = [
('field_one', Integer),
('field_two', OctetString),
('field_three', IA5String, {'optional': True}),
]
```
## SequenceOf
The `SequenceOf` class is used to allow for zero or more instances of a type.
The class uses the `_child_spec` property to define the instance class type.
```python
from asn1crypto.core import SequenceOf, Integer
class Integers(SequenceOf):
_child_spec = Integer
```
Values in the `SequenceOf` can be accessed via `[]` with an integer key. The
length of the `SequenceOf` is determined via `len()`.
```python
values = Integers.load(der_byte_string)
for i in range(0, len(values)):
print(values[i].native)
```
## SetOf
The `SetOf` class is an exact duplicate of `SequenceOf`. According to the ASN.1
standard, the difference is that a `SequenceOf` is explicitly ordered, however
`SetOf` may be in any order. This is an equivalent comparison of a Python `list`
and `set`.
```python
from asn1crypto.core import SetOf, Integer
class Integers(SetOf):
_child_spec = Integer
```
## Integer
The `Integer` class allows values to be *named*. An `Integer` with named values
may contain any integer, however special values with named will be represented
as those names when `.native` is called.
Named values are configured via the `_map` property, which must be a `dict`
with the keys being integers and the values being unicode strings.
```python
from asn1crypto.core import Integer
class Version(Integer):
_map = {
1: 'v1',
2: 'v2',
}
# Will print: "v1"
print(Version(1).native)
# Will print: 4
print(Version(4).native)
```
## Enumerated
The `Enumerated` class is almost identical to `Integer`, however only values in
the `_map` property are valid.
```python
from asn1crypto.core import Enumerated
class Version(Enumerated):
_map = {
1: 'v1',
2: 'v2',
}
# Will print: "v1"
print(Version(1).native)
# Will raise a ValueError exception
print(Version(4).native)
```
## ObjectIdentifier
The `ObjectIdentifier` class represents values of the ASN.1 type of the same
name. `ObjectIdentifier` instances are converted to a unicode string in a
dotted-integer format when `.native` is accessed.
While this standard conversion is a reasonable baseline, in most situations
it will be more maintainable to map the OID strings to a unicode string
containing a description of what the OID repesents.
The mapping of OID strings to name strings is configured via the `_map`
property, which is a `dict` object with keys being unicode OID string and the
values being a unicode string.
The `.dotted` attribute will always return a unicode string of the dotted
integer form of the OID.
The class methods `.map()` and `.unmap()` will convert a dotted integer unicode
string to the user-friendly name, and vice-versa.
```python
from asn1crypto.core import ObjectIdentifier
class MyType(ObjectIdentifier):
_map = {
'1.8.2.1.23': 'value_name',
'1.8.2.1.24': 'other_value',
}
# Will print: "value_name"
print(MyType('1.8.2.1.23').native)
# Will print: "1.8.2.1.23"
print(MyType('1.8.2.1.23').dotted)
# Will print: "1.8.2.1.25"
print(MyType('1.8.2.1.25').native)
# Will print "value_name"
print(MyType.map('1.8.2.1.23'))
# Will print "1.8.2.1.23"
print(MyType.unmap('value_name'))
```
## BitString
When no `_map` is set for a `BitString` class, the native representation is a
`tuple` of `int`s (being either `1` or `0`).
```python
from asn1crypto.core import BitString
b1 = BitString((1, 0, 1))
```
Additionally, it is possible to set the `_map` property to a dict where the
keys are bit indexes and the values are unicode string names. This allows
checking the value of a given bit by item access, and the native representation
becomes a `set` of unicode strings.
```python
from asn1crypto.core import BitString
class MyFlags(BitString):
_map = {
0: 'edit',
1: 'delete',
2: 'manage_users',
}
permissions = MyFlags({'edit', 'delete'})
# This will be printed
if permissions['edit'] and permissions['delete']:
print('Can edit and delete')
# This will not
if 'manage_users' in permissions.native:
print('Is admin')
```
## Strings
ASN.1 contains quite a number of string types:
| Type | Standard Encoding | Implementation Encoding | Notes |
| ----------------- | --------------------------------- | ----------------------- | ------------------------------------------------------------------------- |
| `UTF8String` | UTF-8 | UTF-8 | |
| `NumericString` | ASCII `[0-9 ]` | ISO 8859-1 | The implementation is a superset of supported characters |
| `PrintableString` | ASCII `[a-zA-Z0-9 '()+,\\-./:=?]` | ISO 8859-1 | The implementation is a superset of supported characters |
| `TeletexString` | ITU T.61 | Custom | The implementation is based off of https://en.wikipedia.org/wiki/ITU_T.61 |
| `VideotexString` | *?* | *None* | This has no set encoding, and it not used in cryptography |
| `IA5String` | ITU T.50 (very similar to ASCII) | ISO 8859-1 | The implementation is a superset of supported characters |
| `GraphicString` | * | ISO 8859-1 | This has not set encoding, but seems to often contain ISO 8859-1 |
| `VisibleString` | ASCII (printable) | ISO 8859-1 | The implementation is a superset of supported characters |
| `GeneralString` | * | ISO 8859-1 | This has not set encoding, but seems to often contain ISO 8859-1 |
| `UniversalString` | UTF-32 | UTF-32 | |
| `CharacterString` | * | ISO 8859-1 | This has not set encoding, but seems to often contain ISO 8859-1 |
| `BMPString` | UTF-16 | UTF-16 | |
As noted in the table above, many of the implementations are supersets of the
supported characters. This simplifies parsing, but puts the onus of using valid
characters on the developer. However, in general `UTF8String`, `BMPString` or
`UniversalString` should be preferred when a choice is given.
All string types other than `VideotexString` are created from unicode strings.
```python
from asn1crypto.core import IA5String
print(IA5String('Testing!').native)
```
## UTCTime
The class `UTCTime` accepts a unicode string in one of the formats:
- `%y%m%d%H%MZ`
- `%y%m%d%H%M%SZ`
- `%y%m%d%H%M%z`
- `%y%m%d%H%M%S%z`
or a `datetime.datetime` instance. See the
[Python datetime strptime() reference](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior)
for details of the formats.
When `.native` is accessed, it returns a `datetime.datetime` object with a
`tzinfo` of `asn1crypto.util.timezone.utc`.
## GeneralizedTime
The class `GeneralizedTime` accepts a unicode string in one of the formats:
- `%Y%m%d%H`
- `%Y%m%d%H%M`
- `%Y%m%d%H%M%S`
- `%Y%m%d%H%M%S.%f`
- `%Y%m%d%HZ`
- `%Y%m%d%H%MZ`
- `%Y%m%d%H%M%SZ`
- `%Y%m%d%H%M%S.%fZ`
- `%Y%m%d%H%z`
- `%Y%m%d%H%M%z`
- `%Y%m%d%H%M%S%z`
- `%Y%m%d%H%M%S.%f%z`
or a `datetime.datetime` instance. See the
[Python datetime strptime() reference](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior)
for details of the formats.
When `.native` is accessed, it returns a `datetime.datetime` object with a
`tzinfo` of `asn1crypto.util.timezone.utc`. For formats where the time has a
timezone offset is specified (`[+-]\d{4}`), the time is converted to UTC. For
times without a timezone, the time is assumed to be in UTC.
## Choice
The `Choice` class allows handling ASN.1 Choice structures. The `_alternatives`
property must be set to a `list` containing 2-3 element `tuple`s. The first
element in the tuple is the alternative name. The second element is the type
class for the alternative. The, optional, third element is a `dict` of
parameters to pass to the type class constructor. This is used primarily for
implicit and explicit tagging.
```python
from asn1crypto.core import Choice, Integer, OctetString, IA5String
class MyChoice(Choice):
_alternatives = [
('option_one', Integer),
('option_two', OctetString),
('option_three', IA5String),
]
```
`Choice` objects has two extra properties, `.name` and `.chosen`. The `.name`
property contains the name of the chosen alternative. The `.chosen` property
contains the instance of the chosen type class.
```python
parsed = MyChoice.load(der_bytes)
print(parsed.name)
print(type(parsed.chosen))
```
The `.native` property and `.dump()` method work as with the universal type
classes. Under the hood they just proxy the calls to the `.chosen` object.
## Any
The `Any` class implements the ASN.1 Any type, which allows any data type. By
default objects of this class do not perform any parsing. However, the
`.parse()` instance method allows parsing the contents of the `Any` object,
either into a universal type, or to a specification pass in via the `spec`
parameter.
This type is not used as a top-level structure, but instead allows `Sequence`
and `Set` objects to accept varying contents, usually based on some sort of
`ObjectIdentifier`.
```python
from asn1crypto.core import Sequence, ObjectIdentifier, Any, Integer, OctetString
class MySequence(Sequence):
_fields = [
('type', ObjectIdentifier),
('value', Any),
]
```
## Specification via OID
Throughout the usage of ASN.1 in cryptography, a pattern is present where an
`ObjectIdenfitier` is used to determine what specification should be used to
interpret another field in a `Sequence`. Usually the other field is an instance
of `Any`, however occasionally it is an `OctetString` or `OctetBitString`.
*asn1crypto* provides the `_oid_pair` and `_oid_specs` properties of the
`Sequence` class to allow handling these situations.
The `_oid_pair` is a tuple with two unicode string elements. The first is the
name of the field that is an `ObjectIdentifier` and the second if the name of
the field that has a variable specification based on the first field. *In
situations where the value field should be an `OctetString` or `OctetBitString`,
`ParsableOctetString` and `ParsableOctetBitString` will need to be used instead
to allow for the sub-parsing of the contents.*
The `_oid_specs` property is a `dict` object with `ObjectIdentifier` values as
the keys (either dotted or mapped notation) and a type class as the value. When
the first field in `_oid_pair` has a value equal to one of the keys in
`_oid_specs`, then the corresponding type class will be used as the
specification for the second field of `_oid_pair`.
```python
from asn1crypto.core import Sequence, ObjectIdentifier, Any, OctetString, Integer
class MyId(ObjectIdentifier):
_map = {
'1.2.3.4': 'initialization_vector',
'1.2.3.5': 'iterations',
}
class MySequence(Sequence):
_fields = [
('type', MyId),
('value', Any),
]
_oid_pair = ('type', 'value')
_oid_specs = {
'initialization_vector': OctetString,
'iterations': Integer,
}
```
## Explicit and Implicit Tagging
When working with `Sequence`, `Set` and `Choice` it is often necessary to
disambiguate between fields because of a number of factors:
- In `Sequence` the presence of an optional field must be determined by tag number
- In `Set`, each field must have a different tag number since they can be in any order
- In `Choice`, each alternative must have a different tag number to determine which is present
The universal types all have unique tag numbers. However, if a `Sequence`, `Set`
or `Choice` has more than one field with the same universal type, tagging allows
a way to keep the semantics of the original type, but with a different tag
number.
Implicit tagging simply changes the tag number of a type to a different value.
However, Explicit tagging wraps the existing type in another tag with the
specified tag number.
In general, most situations allow for implicit tagging, with the notable
exception than a field that is a `Choice` type must always be explicitly tagged.
Otherwise, using implicit tagging would modify the tag of the chosen
alternative, breaking the mechanism by which `Choice` works.
Here is an example of implicit and explicit tagging where explicit tagging on
the `Sequence` allows a `Choice` type field to be optional, and where implicit
tagging in the `Choice` structure allows disambiguating between two string of
the same type.
```python
from asn1crypto.core import Sequence, Choice, IA5String, UTCTime, ObjectIdentifier
class Person(Choice):
_alternatives = [
('name', IA5String),
('email', IA5String, {'implicit': 0}),
]
class Record(Sequence):
_fields = [
('id', ObjectIdentifier),
('created', UTCTime),
('creator', Person, {'explicit': 0, 'optional': True}),
]
```
As is shown above, the keys `implicit` and `explicit` are used for tagging,
and are passed to a type class constructor via the optional third element of
a field or alternative tuple. Both parameters may be an integer tag number, or
a 2-element tuple of string class name and integer tag.
If a tagging value needs its tagging changed, the `.untag()` method can be used
to create a copy of the object without explicit/implicit tagging. The `.retag()`
method can be used to change the tagging. This method accepts one parameter, a
dict with either or both of the keys `implicit` and `explicit`.
```python
person = Person(name='email', value='will@wbond.net')
# Will display True
print(person.implicit)
# Will display False
print(person.untag().implicit)
# Will display 0
print(person.tag)
# Will display 1
print(person.retag({'implicit': 1}).tag)
```

View File

@ -9,6 +9,7 @@ A fast, pure Python library for parsing and serializing ASN.1 structures.
- [Dependencies](#dependencies) - [Dependencies](#dependencies)
- [Installation](#installation) - [Installation](#installation)
- [License](#license) - [License](#license)
- [Security Policy](#security-policy)
- [Documentation](#documentation) - [Documentation](#documentation)
- [Continuous Integration](#continuous-integration) - [Continuous Integration](#continuous-integration)
- [Testing](#testing) - [Testing](#testing)
@ -109,11 +110,11 @@ faster to an order of magnitude or more.
## Current Release ## Current Release
1.4.0 - [changelog](changelog.md) 1.5.0 - [changelog](changelog.md)
## Dependencies ## Dependencies
Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9 or pypy. *No third-party Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 or pypy. *No third-party
packages required.* packages required.*
## Installation ## Installation
@ -127,6 +128,11 @@ pip install asn1crypto
*asn1crypto* is licensed under the terms of the MIT license. See the *asn1crypto* is licensed under the terms of the MIT license. See the
[LICENSE](LICENSE) file for the exact license text. [LICENSE](LICENSE) file for the exact license text.
## Security Policy
The security policies for this project are covered in
[SECURITY.md](https://github.com/wbond/asn1crypto/blob/master/SECURITY.md).
## Documentation ## Documentation
The documentation for *asn1crypto* is composed of tutorials on basic usage and The documentation for *asn1crypto* is composed of tutorials on basic usage and

View File

@ -0,0 +1,4 @@
[egg_info]
tag_build =
tag_date = 0

View File

@ -10,7 +10,7 @@ from setuptools.command.egg_info import egg_info
PACKAGE_NAME = 'asn1crypto' PACKAGE_NAME = 'asn1crypto'
PACKAGE_VERSION = '1.4.0' PACKAGE_VERSION = '1.5.1'
PACKAGE_ROOT = os.path.dirname(os.path.abspath(__file__)) PACKAGE_ROOT = os.path.dirname(os.path.abspath(__file__))
@ -138,6 +138,7 @@ setup(
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy', 'Programming Language :: Python :: Implementation :: PyPy',

View File

@ -1 +1 @@
2021-12-15-01 2022-07-31-01

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Will Bond <will@wbond.net> Copyright (c) 2015-2022 Will Bond <will@wbond.net>
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in this software and associated documentation files (the "Software"), to deal in

View File

@ -1,6 +1,6 @@
Metadata-Version: 2.1 Metadata-Version: 2.1
Name: oscrypto Name: oscrypto
Version: 1.2.1 Version: 1.3.0
Summary: TLS (SSL) sockets, key generation, encryption, decryption, signing, verification and KDFs using the OS crypto libraries. Does not require a compiler, and relies on the OS for patching. Works on Windows, OS X and Linux/BSD. Summary: TLS (SSL) sockets, key generation, encryption, decryption, signing, verification and KDFs using the OS crypto libraries. Does not require a compiler, and relies on the OS for patching. Works on Windows, OS X and Linux/BSD.
Home-page: https://github.com/wbond/oscrypto Home-page: https://github.com/wbond/oscrypto
Author: wbond Author: wbond
@ -10,7 +10,7 @@ Description: # oscrypto
A compilation-free, always up-to-date encryption library for Python that works A compilation-free, always up-to-date encryption library for Python that works
on Windows, OS X, Linux and BSD. Supports the following versions of Python: on Windows, OS X, Linux and BSD. Supports the following versions of Python:
2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8 and pypy. 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 and pypy.
- [Supported Operating Systems](#supported-operationg-systems) - [Supported Operating Systems](#supported-operationg-systems)
- [Features](#features) - [Features](#features)
@ -27,8 +27,6 @@ Description: # oscrypto
- [CI Tasks](#ci-tasks) - [CI Tasks](#ci-tasks)
[![GitHub Actions CI](https://github.com/wbond/oscrypto/workflows/CI/badge.svg)](https://github.com/wbond/oscrypto/actions?workflow=CI) [![GitHub Actions CI](https://github.com/wbond/oscrypto/workflows/CI/badge.svg)](https://github.com/wbond/oscrypto/actions?workflow=CI)
[![Travis CI](https://api.travis-ci.org/wbond/oscrypto.svg?branch=master)](https://travis-ci.org/wbond/oscrypto)
[![AppVeyor](https://ci.appveyor.com/api/projects/status/github/wbond/oscrypto?branch=master&svg=true)](https://ci.appveyor.com/project/wbond/oscrypto)
[![CircleCI](https://circleci.com/gh/wbond/oscrypto.svg?style=shield)](https://circleci.com/gh/wbond/oscrypto) [![CircleCI](https://circleci.com/gh/wbond/oscrypto.svg?style=shield)](https://circleci.com/gh/wbond/oscrypto)
[![PyPI](https://img.shields.io/pypi/v/oscrypto.svg)](https://pypi.python.org/pypi/oscrypto) [![PyPI](https://img.shields.io/pypi/v/oscrypto.svg)](https://pypi.python.org/pypi/oscrypto)
@ -66,12 +64,15 @@ Description: # oscrypto
- macOS 10.13 with LibreSSL 2.2.7 - macOS 10.13 with LibreSSL 2.2.7
- macOS 10.14 - macOS 10.14
- macOS 10.15 - macOS 10.15
- macOS 10.15 with OpenSSL 3.0
- macOS 11 - macOS 11
- macOS 12
- Linux or BSD - Linux or BSD
- Uses one of: - Uses one of:
- [OpenSSL 0.9.8](https://www.openssl.org/docs/man0.9.8/) - [OpenSSL 0.9.8](https://www.openssl.org/docs/man0.9.8/)
- [OpenSSL 1.0.x](https://www.openssl.org/docs/man1.0.0/) - [OpenSSL 1.0.x](https://www.openssl.org/docs/man1.0.0/)
- [OpenSSL 1.1.0](https://www.openssl.org/docs/man1.1.0/) - [OpenSSL 1.1.0](https://www.openssl.org/docs/man1.1.0/)
- [OpenSSL 3.0](https://www.openssl.org/docs/man3.0/)
- [LibreSSL](http://www.libressl.org/) - [LibreSSL](http://www.libressl.org/)
- Tested on: - Tested on:
- Arch Linux with OpenSSL 1.0.2 - Arch Linux with OpenSSL 1.0.2
@ -81,6 +82,7 @@ Description: # oscrypto
- Ubuntu 15.04 with OpenSSL 1.0.1 - Ubuntu 15.04 with OpenSSL 1.0.1
- Ubuntu 16.04 with OpenSSL 1.0.2 on Raspberry Pi 3 (armhf) - Ubuntu 16.04 with OpenSSL 1.0.2 on Raspberry Pi 3 (armhf)
- Ubuntu 18.04 with OpenSSL 1.1.x (amd64, arm64, ppc64el) - Ubuntu 18.04 with OpenSSL 1.1.x (amd64, arm64, ppc64el)
- Ubuntu 22.04 with OpenSSL 3.0 (amd64)
*OS X 10.6 will not be supported due to a lack of available *OS X 10.6 will not be supported due to a lack of available
cryptographic primitives and due to lack of vendor support.* cryptographic primitives and due to lack of vendor support.*
@ -207,7 +209,10 @@ Description: # oscrypto
## Dependencies ## Dependencies
- [*asn1crypto*](https://github.com/wbond/asn1crypto) - [*asn1crypto*](https://github.com/wbond/asn1crypto)
- Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8 or pypy - Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 or pypy
- OpenSSL/LibreSSL if on Linux¹
*¹ On Linux, `ctypes.util.find_library()` is used to located OpenSSL. Alpine Linux does not have an appropriate install by default for `find_library()` to work properly. Instead, `oscrypto.use_openssl()` must be called with the path to the OpenSSL shared libraries.*
## Installation ## Installation
@ -228,10 +233,8 @@ Description: # oscrypto
Various combinations of platforms and versions of Python are tested via: Various combinations of platforms and versions of Python are tested via:
- [AppVeyor](https://ci.appveyor.com/project/wbond/oscrypto/history) - [macOS, Linux, Windows](https://github.com/wbond/oscrypto/actions/workflows/ci.yml) via GitHub Actions
- [CircleCI](https://circleci.com/gh/wbond/oscrypto) - [arm64](https://circleci.com/gh/wbond/oscrypto) via CircleCI
- [GitHub Actions](https://github.com/wbond/oscrypto/actions)
- [Travis CI](https://travis-ci.org/wbond/oscrypto/builds)
## Testing ## Testing
@ -264,11 +267,36 @@ Description: # oscrypto
python run.py tests aes 20 python run.py tests aes 20
``` ```
#### Backend Options
To run tests using a custom build of OpenSSL, or to use OpenSSL on Windows or To run tests using a custom build of OpenSSL, or to use OpenSSL on Windows or
Mac, add `use_openssl` after `run.py`, like: Mac, add `use_openssl` after `run.py`, like:
```bash ```bash
python run.py use_openssl=/path/to/libcrypto.dylib,/path/to/libssl.dylib tests python run.py use_openssl=/path/to/libcrypto.so,/path/to/libssl.so tests
```
To run tests forcing the use of ctypes, even if cffi is installed, add
`use_ctypes` after `run.py`:
```bash
python run.py use_ctypes=true tests
```
To run tests using the legacy Windows crypto functions on Windows 7+, add
`use_winlegacy` after `run.py`:
```bash
python run.py use_winlegacy=true tests
```
#### Internet Tests
To skip tests that require an internet connection, add `skip_internet` after
`run.py`:
```bash
python run.py skip_internet=true tests
``` ```
### PyPi Source Distribution ### PyPi Source Distribution
@ -280,6 +308,45 @@ Description: # oscrypto
python setup.py test python setup.py test
``` ```
#### Test Options
The following env vars can control aspects of running tests:
##### Force OpenSSL Shared Library Paths
Setting the env var `OSCRYPTO_USE_OPENSSL` to a string in the form:
```
/path/to/libcrypto.so,/path/to/libssl.so
```
will force use of specific OpenSSL shared libraries.
This also works on Mac and Windows to force use of OpenSSL instead of using
native crypto libraries.
##### Force Use of ctypes
By default, oscrypto will use the `cffi` module for FFI if it is installed.
To use the slightly slower, but more widely-tested, `ctypes` FFI layer, set
the env var `OPENSSL_USE_CTYPES=true`.
##### Force Use of Legacy Windows Crypto APIs
On Windows 7 and newer, oscrypto will use the CNG backend by default.
To force use of the older CryptoAPI, set the env var
`OPENSSL_USE_WINLEGACY=true`.
##### Skip Tests Requiring an Internet Connection
Some of the TLS tests require an active internet connection to ensure that
various "bad" server certificates are rejected.
To skip tests requiring an internet connection, set the env var
`OPENSSL_SKIP_INTERNET_TESTS=true`.
### Package ### Package
When the package has been installed via pip (or another method), the package When the package has been installed via pip (or another method), the package
@ -353,7 +420,7 @@ Description: # oscrypto
related to getting pip to work properly and messing with `site-packages` for related to getting pip to work properly and messing with `site-packages` for
the version of Python being used. the version of Python being used.
The `ci` task runs `lint` (if flake8 is avaiable for the version of Python) and The `ci` task runs `lint` (if flake8 is available for the version of Python) and
`coverage` (or `tests` if coverage is not available for the version of Python). `coverage` (or `tests` if coverage is not available for the version of Python).
If the current directory is a clean git working copy, the coverage data is If the current directory is a clean git working copy, the coverage data is
submitted to codecov.io. submitted to codecov.io.
@ -368,8 +435,10 @@ Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.2
Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.4
@ -377,6 +446,8 @@ Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Security :: Cryptography
Description-Content-Type: text/markdown Description-Content-Type: text/markdown

View File

@ -1,5 +1,22 @@
# changelog # changelog
## 1.3.0
- Add support for OpenSSL 3.0
- Add first-class support for RSASSA-PSS certificates
- Add user-friendly handling of the error message with TLS on macOS
when a ceritificate has a lifetime that is longer than the CAB forum
guidelines
- Fix AES 192/256 encryption on OpenSSL and Windows to allow no padding when
plaintext is an exact multiple of 16 bytes long. Previously AES192 would
require plaintext with a length that was a multiple of 24 AND 16, and
AES256 would require plaintext with a length that was a multiple of 32.
- Add the ability to skip tests that require internet connectivity
*via @jnahmias*
- Fix a bug throwing an exception when passing an invalid type to
`asymmetric.load_public_key()` *via @Arbitrage0*
- Fix a number of typos in doc strings *via @frennkie and @kianmeng*
## 1.2.1 ## 1.2.1
- Fix running in an environment with a custom OpenSSL install on macOS 10.15 - Fix running in an environment with a custom OpenSSL install on macOS 10.15

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Will Bond <will@wbond.net> Copyright (c) 2015-2022 Will Bond <will@wbond.net>
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in this software and associated documentation files (the "Software"), to deal in

View File

@ -1,6 +1,6 @@
Metadata-Version: 2.1 Metadata-Version: 2.1
Name: oscrypto Name: oscrypto
Version: 1.2.1 Version: 1.3.0
Summary: TLS (SSL) sockets, key generation, encryption, decryption, signing, verification and KDFs using the OS crypto libraries. Does not require a compiler, and relies on the OS for patching. Works on Windows, OS X and Linux/BSD. Summary: TLS (SSL) sockets, key generation, encryption, decryption, signing, verification and KDFs using the OS crypto libraries. Does not require a compiler, and relies on the OS for patching. Works on Windows, OS X and Linux/BSD.
Home-page: https://github.com/wbond/oscrypto Home-page: https://github.com/wbond/oscrypto
Author: wbond Author: wbond
@ -10,7 +10,7 @@ Description: # oscrypto
A compilation-free, always up-to-date encryption library for Python that works A compilation-free, always up-to-date encryption library for Python that works
on Windows, OS X, Linux and BSD. Supports the following versions of Python: on Windows, OS X, Linux and BSD. Supports the following versions of Python:
2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8 and pypy. 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 and pypy.
- [Supported Operating Systems](#supported-operationg-systems) - [Supported Operating Systems](#supported-operationg-systems)
- [Features](#features) - [Features](#features)
@ -27,8 +27,6 @@ Description: # oscrypto
- [CI Tasks](#ci-tasks) - [CI Tasks](#ci-tasks)
[![GitHub Actions CI](https://github.com/wbond/oscrypto/workflows/CI/badge.svg)](https://github.com/wbond/oscrypto/actions?workflow=CI) [![GitHub Actions CI](https://github.com/wbond/oscrypto/workflows/CI/badge.svg)](https://github.com/wbond/oscrypto/actions?workflow=CI)
[![Travis CI](https://api.travis-ci.org/wbond/oscrypto.svg?branch=master)](https://travis-ci.org/wbond/oscrypto)
[![AppVeyor](https://ci.appveyor.com/api/projects/status/github/wbond/oscrypto?branch=master&svg=true)](https://ci.appveyor.com/project/wbond/oscrypto)
[![CircleCI](https://circleci.com/gh/wbond/oscrypto.svg?style=shield)](https://circleci.com/gh/wbond/oscrypto) [![CircleCI](https://circleci.com/gh/wbond/oscrypto.svg?style=shield)](https://circleci.com/gh/wbond/oscrypto)
[![PyPI](https://img.shields.io/pypi/v/oscrypto.svg)](https://pypi.python.org/pypi/oscrypto) [![PyPI](https://img.shields.io/pypi/v/oscrypto.svg)](https://pypi.python.org/pypi/oscrypto)
@ -66,12 +64,15 @@ Description: # oscrypto
- macOS 10.13 with LibreSSL 2.2.7 - macOS 10.13 with LibreSSL 2.2.7
- macOS 10.14 - macOS 10.14
- macOS 10.15 - macOS 10.15
- macOS 10.15 with OpenSSL 3.0
- macOS 11 - macOS 11
- macOS 12
- Linux or BSD - Linux or BSD
- Uses one of: - Uses one of:
- [OpenSSL 0.9.8](https://www.openssl.org/docs/man0.9.8/) - [OpenSSL 0.9.8](https://www.openssl.org/docs/man0.9.8/)
- [OpenSSL 1.0.x](https://www.openssl.org/docs/man1.0.0/) - [OpenSSL 1.0.x](https://www.openssl.org/docs/man1.0.0/)
- [OpenSSL 1.1.0](https://www.openssl.org/docs/man1.1.0/) - [OpenSSL 1.1.0](https://www.openssl.org/docs/man1.1.0/)
- [OpenSSL 3.0](https://www.openssl.org/docs/man3.0/)
- [LibreSSL](http://www.libressl.org/) - [LibreSSL](http://www.libressl.org/)
- Tested on: - Tested on:
- Arch Linux with OpenSSL 1.0.2 - Arch Linux with OpenSSL 1.0.2
@ -81,6 +82,7 @@ Description: # oscrypto
- Ubuntu 15.04 with OpenSSL 1.0.1 - Ubuntu 15.04 with OpenSSL 1.0.1
- Ubuntu 16.04 with OpenSSL 1.0.2 on Raspberry Pi 3 (armhf) - Ubuntu 16.04 with OpenSSL 1.0.2 on Raspberry Pi 3 (armhf)
- Ubuntu 18.04 with OpenSSL 1.1.x (amd64, arm64, ppc64el) - Ubuntu 18.04 with OpenSSL 1.1.x (amd64, arm64, ppc64el)
- Ubuntu 22.04 with OpenSSL 3.0 (amd64)
*OS X 10.6 will not be supported due to a lack of available *OS X 10.6 will not be supported due to a lack of available
cryptographic primitives and due to lack of vendor support.* cryptographic primitives and due to lack of vendor support.*
@ -207,7 +209,10 @@ Description: # oscrypto
## Dependencies ## Dependencies
- [*asn1crypto*](https://github.com/wbond/asn1crypto) - [*asn1crypto*](https://github.com/wbond/asn1crypto)
- Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8 or pypy - Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 or pypy
- OpenSSL/LibreSSL if on Linux¹
*¹ On Linux, `ctypes.util.find_library()` is used to located OpenSSL. Alpine Linux does not have an appropriate install by default for `find_library()` to work properly. Instead, `oscrypto.use_openssl()` must be called with the path to the OpenSSL shared libraries.*
## Installation ## Installation
@ -228,10 +233,8 @@ Description: # oscrypto
Various combinations of platforms and versions of Python are tested via: Various combinations of platforms and versions of Python are tested via:
- [AppVeyor](https://ci.appveyor.com/project/wbond/oscrypto/history) - [macOS, Linux, Windows](https://github.com/wbond/oscrypto/actions/workflows/ci.yml) via GitHub Actions
- [CircleCI](https://circleci.com/gh/wbond/oscrypto) - [arm64](https://circleci.com/gh/wbond/oscrypto) via CircleCI
- [GitHub Actions](https://github.com/wbond/oscrypto/actions)
- [Travis CI](https://travis-ci.org/wbond/oscrypto/builds)
## Testing ## Testing
@ -264,11 +267,36 @@ Description: # oscrypto
python run.py tests aes 20 python run.py tests aes 20
``` ```
#### Backend Options
To run tests using a custom build of OpenSSL, or to use OpenSSL on Windows or To run tests using a custom build of OpenSSL, or to use OpenSSL on Windows or
Mac, add `use_openssl` after `run.py`, like: Mac, add `use_openssl` after `run.py`, like:
```bash ```bash
python run.py use_openssl=/path/to/libcrypto.dylib,/path/to/libssl.dylib tests python run.py use_openssl=/path/to/libcrypto.so,/path/to/libssl.so tests
```
To run tests forcing the use of ctypes, even if cffi is installed, add
`use_ctypes` after `run.py`:
```bash
python run.py use_ctypes=true tests
```
To run tests using the legacy Windows crypto functions on Windows 7+, add
`use_winlegacy` after `run.py`:
```bash
python run.py use_winlegacy=true tests
```
#### Internet Tests
To skip tests that require an internet connection, add `skip_internet` after
`run.py`:
```bash
python run.py skip_internet=true tests
``` ```
### PyPi Source Distribution ### PyPi Source Distribution
@ -280,6 +308,45 @@ Description: # oscrypto
python setup.py test python setup.py test
``` ```
#### Test Options
The following env vars can control aspects of running tests:
##### Force OpenSSL Shared Library Paths
Setting the env var `OSCRYPTO_USE_OPENSSL` to a string in the form:
```
/path/to/libcrypto.so,/path/to/libssl.so
```
will force use of specific OpenSSL shared libraries.
This also works on Mac and Windows to force use of OpenSSL instead of using
native crypto libraries.
##### Force Use of ctypes
By default, oscrypto will use the `cffi` module for FFI if it is installed.
To use the slightly slower, but more widely-tested, `ctypes` FFI layer, set
the env var `OPENSSL_USE_CTYPES=true`.
##### Force Use of Legacy Windows Crypto APIs
On Windows 7 and newer, oscrypto will use the CNG backend by default.
To force use of the older CryptoAPI, set the env var
`OPENSSL_USE_WINLEGACY=true`.
##### Skip Tests Requiring an Internet Connection
Some of the TLS tests require an active internet connection to ensure that
various "bad" server certificates are rejected.
To skip tests requiring an internet connection, set the env var
`OPENSSL_SKIP_INTERNET_TESTS=true`.
### Package ### Package
When the package has been installed via pip (or another method), the package When the package has been installed via pip (or another method), the package
@ -353,7 +420,7 @@ Description: # oscrypto
related to getting pip to work properly and messing with `site-packages` for related to getting pip to work properly and messing with `site-packages` for
the version of Python being used. the version of Python being used.
The `ci` task runs `lint` (if flake8 is avaiable for the version of Python) and The `ci` task runs `lint` (if flake8 is available for the version of Python) and
`coverage` (or `tests` if coverage is not available for the version of Python). `coverage` (or `tests` if coverage is not available for the version of Python).
If the current directory is a clean git working copy, the coverage data is If the current directory is a clean git working copy, the coverage data is
submitted to codecov.io. submitted to codecov.io.
@ -368,8 +435,10 @@ Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.2
Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.4
@ -377,6 +446,8 @@ Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Security :: Cryptography
Description-Content-Type: text/markdown Description-Content-Type: text/markdown

View File

@ -1,3 +1,4 @@
LICENSE
setup.py setup.py
oscrypto/__init__.py oscrypto/__init__.py
oscrypto/_asn1.py oscrypto/_asn1.py

View File

@ -1 +1 @@
asn1crypto>=1.0.0 asn1crypto>=1.5.1

View File

@ -241,10 +241,12 @@ def _unwrap_private_key_info(key_info):
- asn1crypto.keys.ECPrivateKey - asn1crypto.keys.ECPrivateKey
""" """
if key_info.algorithm == 'rsa': key_alg = key_info.algorithm
if key_alg == 'rsa' or key_alg == 'rsassa_pss':
return key_info['private_key'].parsed return key_info['private_key'].parsed
if key_info.algorithm == 'dsa': if key_alg == 'dsa':
params = key_info['private_key_algorithm']['parameters'] params = key_info['private_key_algorithm']['parameters']
parsed = key_info['private_key'].parsed parsed = key_info['private_key'].parsed
return DSAPrivateKey({ return DSAPrivateKey({
@ -260,7 +262,7 @@ def _unwrap_private_key_info(key_info):
'private_key': parsed, 'private_key': parsed,
}) })
if key_info.algorithm == 'ec': if key_alg == 'ec':
parsed = key_info['private_key'].parsed parsed = key_info['private_key'].parsed
parsed['parameters'] = key_info['private_key_algorithm']['parameters'] parsed['parameters'] = key_info['private_key_algorithm']['parameters']
return parsed return parsed
@ -660,7 +662,7 @@ def _unarmor_pem(data, password=None):
data = data.strip() data = data.strip()
# RSA private keys are encrypted after being DER-encoded, but before base64 # RSA private keys are encrypted after being DER-encoded, but before base64
# encoding, so they need to be hanlded specially # encoding, so they need to be handled specially
if pem_header in set(['RSA PRIVATE KEY', 'DSA PRIVATE KEY', 'EC PRIVATE KEY']): if pem_header in set(['RSA PRIVATE KEY', 'DSA PRIVATE KEY', 'EC PRIVATE KEY']):
algo = armor_type.group(2).lower() algo = armor_type.group(2).lower()
return ('private key', algo, _unarmor_pem_openssl_private(headers, der_bytes, password)) return ('private key', algo, _unarmor_pem_openssl_private(headers, der_bytes, password))

View File

@ -119,6 +119,7 @@ class SecurityConst():
CSSMERR_TP_CERT_NOT_VALID_YET = -2147409653 CSSMERR_TP_CERT_NOT_VALID_YET = -2147409653
CSSMERR_TP_CERT_REVOKED = -2147409652 CSSMERR_TP_CERT_REVOKED = -2147409652
CSSMERR_TP_NOT_TRUSTED = -2147409622 CSSMERR_TP_NOT_TRUSTED = -2147409622
CSSMERR_TP_CERT_SUSPENDED = -2147409651
CSSM_CERT_X_509v3 = 0x00000004 CSSM_CERT_X_509v3 = 0x00000004

View File

@ -250,8 +250,18 @@ class Certificate(_CertificateBase):
""" """
if not self._public_key and self.sec_certificate_ref: if not self._public_key and self.sec_certificate_ref:
if self.asn1.signature_algo == "rsassa_pss":
# macOS doesn't like importing RSA PSS certs, so we treat it like a
# traditional RSA cert
asn1 = self.asn1.copy()
asn1['tbs_certificate']['subject_public_key_info']['algorithm']['algorithm'] = 'rsa'
temp_cert = _load_x509(asn1)
sec_cert_ref = temp_cert.sec_certificate_ref
else:
sec_cert_ref = self.sec_certificate_ref
sec_public_key_ref_pointer = new(Security, 'SecKeyRef *') sec_public_key_ref_pointer = new(Security, 'SecKeyRef *')
res = Security.SecCertificateCopyPublicKey(self.sec_certificate_ref, sec_public_key_ref_pointer) res = Security.SecCertificateCopyPublicKey(sec_cert_ref, sec_public_key_ref_pointer)
handle_sec_error(res) handle_sec_error(res)
sec_public_key_ref = unwrap(sec_public_key_ref_pointer) sec_public_key_ref = unwrap(sec_public_key_ref_pointer)
self._public_key = PublicKey(sec_public_key_ref, self.asn1['tbs_certificate']['subject_public_key_info']) self._public_key = PublicKey(sec_public_key_ref, self.asn1['tbs_certificate']['subject_public_key_info'])
@ -274,6 +284,8 @@ class Certificate(_CertificateBase):
if signature_algo == 'rsassa_pkcs1v15': if signature_algo == 'rsassa_pkcs1v15':
verify_func = rsa_pkcs1v15_verify verify_func = rsa_pkcs1v15_verify
elif signature_algo == 'rsassa_pss':
verify_func = rsa_pss_verify
elif signature_algo == 'dsa': elif signature_algo == 'dsa':
verify_func = dsa_verify verify_func = dsa_verify
elif signature_algo == 'ecdsa': elif signature_algo == 'ecdsa':
@ -832,6 +844,13 @@ def _load_key(key_object):
)) ))
if isinstance(key_object, PublicKeyInfo): if isinstance(key_object, PublicKeyInfo):
if key_object.algorithm == 'rsassa_pss':
# We have to masquerade an RSA PSS key as plain RSA or it won't
# import properly
temp_key_object = key_object.copy()
temp_key_object['algorithm']['algorithm'] = 'rsa'
source = temp_key_object.dump()
else:
source = key_object.dump() source = key_object.dump()
item_type = SecurityConst.kSecItemTypePublicKey item_type = SecurityConst.kSecItemTypePublicKey
@ -1392,7 +1411,8 @@ def rsa_pss_verify(certificate_or_public_key, signature, data, hash_algorithm):
type_name(data) type_name(data)
)) ))
if certificate_or_public_key.algorithm != 'rsa': cp_algo = certificate_or_public_key.algorithm
if cp_algo != 'rsa' and cp_algo != 'rsassa_pss':
raise ValueError('The key specified is not an RSA public key') raise ValueError('The key specified is not an RSA public key')
hash_length = { hash_length = {
@ -1735,7 +1755,8 @@ def rsa_pss_sign(private_key, data, hash_algorithm):
type_name(data) type_name(data)
)) ))
if private_key.algorithm != 'rsa': pk_algo = private_key.algorithm
if pk_algo != 'rsa' and pk_algo != 'rsassa_pss':
raise ValueError('The key specified is not an RSA private key') raise ValueError('The key specified is not an RSA private key')
hash_length = { hash_length = {

View File

@ -50,6 +50,7 @@ from .._tls import (
raise_expired_not_yet_valid, raise_expired_not_yet_valid,
raise_handshake, raise_handshake,
raise_hostname, raise_hostname,
raise_lifetime_too_long,
raise_no_issuer, raise_no_issuer,
raise_protocol_error, raise_protocol_error,
raise_protocol_version, raise_protocol_version,
@ -103,7 +104,7 @@ def _read_callback(connection_id, data_buffer, data_length_pointer):
Callback called by Secure Transport to actually read the socket Callback called by Secure Transport to actually read the socket
:param connection_id: :param connection_id:
An integer identifing the connection An integer identifying the connection
:param data_buffer: :param data_buffer:
A char pointer FFI type to write the data to A char pointer FFI type to write the data to
@ -218,7 +219,7 @@ def _write_callback(connection_id, data_buffer, data_length_pointer):
Callback called by Secure Transport to actually write to the socket Callback called by Secure Transport to actually write to the socket
:param connection_id: :param connection_id:
An integer identifing the connection An integer identifying the connection
:param data_buffer: :param data_buffer:
A char pointer FFI type containing the data to write A char pointer FFI type containing the data to write
@ -463,7 +464,7 @@ class TLSSocket(object):
def __init__(self, address, port, timeout=10, session=None): def __init__(self, address, port, timeout=10, session=None):
""" """
:param address: :param address:
A unicode string of the domain name or IP address to conenct to A unicode string of the domain name or IP address to connect to
:param port: :param port:
An integer of the port number to connect to An integer of the port number to connect to
@ -875,6 +876,7 @@ class TLSSocket(object):
expired = result_code == SecurityConst.CSSMERR_TP_CERT_EXPIRED expired = result_code == SecurityConst.CSSMERR_TP_CERT_EXPIRED
not_yet_valid = result_code == SecurityConst.CSSMERR_TP_CERT_NOT_VALID_YET not_yet_valid = result_code == SecurityConst.CSSMERR_TP_CERT_NOT_VALID_YET
bad_hostname = result_code == SecurityConst.CSSMERR_APPLETP_HOSTNAME_MISMATCH bad_hostname = result_code == SecurityConst.CSSMERR_APPLETP_HOSTNAME_MISMATCH
validity_too_long = result_code == SecurityConst.CSSMERR_TP_CERT_SUSPENDED
# On macOS 10.12, some expired certificates return errSSLInternal # On macOS 10.12, some expired certificates return errSSLInternal
if osx_version_info >= (10, 12): if osx_version_info >= (10, 12):
@ -903,6 +905,9 @@ class TLSSocket(object):
elif self_signed: elif self_signed:
raise_self_signed(cert) raise_self_signed(cert)
elif validity_too_long:
raise_lifetime_too_long(cert)
if detect_client_auth_request(self._server_hello): if detect_client_auth_request(self._server_hello):
raise_client_auth() raise_client_auth()

View File

@ -5,6 +5,15 @@ from .. import ffi
from .._ffi import buffer_from_bytes, byte_string_from_buffer, null from .._ffi import buffer_from_bytes, byte_string_from_buffer, null
from .._types import str_cls from .._types import str_cls
# This file has been modified to work on OpenSSL 3.
# For more information see the readme.md in the root of the archive,
# or check out this PR: https://github.com/wbond/oscrypto/pull/61
if ffi() == 'cffi': if ffi() == 'cffi':
from ._libcrypto_cffi import ( from ._libcrypto_cffi import (
libcrypto, libcrypto,
@ -22,6 +31,7 @@ else:
__all__ = [ __all__ = [
'handle_openssl_error', 'handle_openssl_error',
'libcrypto', 'libcrypto',
'libcrypto_legacy_support',
'libcrypto_version', 'libcrypto_version',
'libcrypto_version_info', 'libcrypto_version_info',
'LibcryptoConst', 'LibcryptoConst',
@ -38,6 +48,18 @@ if libcrypto_version_info < (1, 1):
libcrypto.OPENSSL_config(null()) libcrypto.OPENSSL_config(null())
# This enables legacy algorithms in OpenSSL 3.0, such as RC2, etc
# which are used by various tests and some old protocols and things
# like PKCS12
libcrypto_legacy_support = True
if libcrypto_version_info >= (3, ):
libcrypto.OSSL_PROVIDER_load(null(), "legacy".encode("ascii"))
libcrypto.OSSL_PROVIDER_load(null(), "default".encode("ascii"))
if libcrypto.OSSL_PROVIDER_available(null(), "legacy".encode("ascii")) == 0:
libcrypto_legacy_support = False
def _try_decode(value): def _try_decode(value):
try: try:
@ -57,7 +79,7 @@ def _try_decode(value):
def handle_openssl_error(result, exception_class=None): def handle_openssl_error(result, exception_class=None):
""" """
Checks if an error occured, and if so throws an OSError containing the Checks if an error occurred, and if so throws an OSError containing the
last OpenSSL error message last OpenSSL error message
:param result: :param result:
@ -95,9 +117,15 @@ def peek_openssl_error():
""" """
error = libcrypto.ERR_peek_error() error = libcrypto.ERR_peek_error()
if libcrypto_version_info < (3, 0):
lib = int((error >> 24) & 0xff) lib = int((error >> 24) & 0xff)
func = int((error >> 12) & 0xfff) func = int((error >> 12) & 0xfff)
reason = int(error & 0xfff) reason = int(error & 0xfff)
else:
lib = int((error >> 23) & 0xff)
# OpenSSL 3.0 removed ERR_GET_FUNC()
func = 0
reason = int(error & 0x7fffff)
return (lib, func, reason) return (lib, func, reason)

View File

@ -0,0 +1,149 @@
# coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function
from .. import ffi
from .._ffi import buffer_from_bytes, byte_string_from_buffer, null
from .._types import str_cls
if ffi() == 'cffi':
from ._libcrypto_cffi import (
libcrypto,
version as libcrypto_version,
version_info as libcrypto_version_info
)
else:
from ._libcrypto_ctypes import (
libcrypto,
version as libcrypto_version,
version_info as libcrypto_version_info
)
__all__ = [
'handle_openssl_error',
'libcrypto',
'libcrypto_legacy_support',
'libcrypto_version',
'libcrypto_version_info',
'LibcryptoConst',
'peek_openssl_error',
]
_encoding = 'utf-8'
_fallback_encodings = ['utf-8', 'cp1252']
if libcrypto_version_info < (1, 1):
libcrypto.ERR_load_crypto_strings()
libcrypto.OPENSSL_config(null())
# This enables legacy algorithms in OpenSSL 3.0, such as RC2, etc
# which are used by various tests and some old protocols and things
# like PKCS12
libcrypto_legacy_support = True
if libcrypto_version_info >= (3, ):
if libcrypto.OSSL_PROVIDER_available(null(), "legacy".encode("ascii")):
libcrypto.OSSL_PROVIDER_load(null(), "legacy".encode("ascii"))
else:
libcrypto_legacy_support = False
def _try_decode(value):
try:
return str_cls(value, _encoding)
# If the "correct" encoding did not work, try some defaults, and then just
# obliterate characters that we can't seen to decode properly
except (UnicodeDecodeError):
for encoding in _fallback_encodings:
try:
return str_cls(value, encoding, errors='strict')
except (UnicodeDecodeError):
pass
return str_cls(value, errors='replace')
def handle_openssl_error(result, exception_class=None):
"""
Checks if an error occurred, and if so throws an OSError containing the
last OpenSSL error message
:param result:
An integer result code - 1 or greater indicates success
:param exception_class:
The exception class to use for the exception if an error occurred
:raises:
OSError - when an OpenSSL error occurs
"""
if result > 0:
return
if exception_class is None:
exception_class = OSError
error_num = libcrypto.ERR_get_error()
buffer = buffer_from_bytes(120)
libcrypto.ERR_error_string(error_num, buffer)
# Since we are dealing with a string, it is NULL terminated
error_string = byte_string_from_buffer(buffer)
raise exception_class(_try_decode(error_string))
def peek_openssl_error():
"""
Peeks into the error stack and pulls out the lib, func and reason
:return:
A three-element tuple of integers (lib, func, reason)
"""
error = libcrypto.ERR_peek_error()
if libcrypto_version_info < (3, 0):
lib = int((error >> 24) & 0xff)
func = int((error >> 12) & 0xfff)
reason = int(error & 0xfff)
else:
lib = int((error >> 23) & 0xff)
# OpenSSL 3.0 removed ERR_GET_FUNC()
func = 0
reason = int(error & 0x7fffff)
return (lib, func, reason)
class LibcryptoConst():
EVP_CTRL_SET_RC2_KEY_BITS = 3
SSLEAY_VERSION = 0
RSA_PKCS1_PADDING = 1
RSA_NO_PADDING = 3
RSA_PKCS1_OAEP_PADDING = 4
# OpenSSL 0.9.x
EVP_MD_CTX_FLAG_PSS_MDLEN = -1
# OpenSSL 1.x.x
EVP_PKEY_CTRL_RSA_PADDING = 0x1001
RSA_PKCS1_PSS_PADDING = 6
EVP_PKEY_CTRL_RSA_PSS_SALTLEN = 0x1002
EVP_PKEY_RSA = 6
EVP_PKEY_OP_SIGN = 1 << 3
EVP_PKEY_OP_VERIFY = 1 << 4
NID_X9_62_prime256v1 = 415
NID_secp384r1 = 715
NID_secp521r1 = 716
OPENSSL_EC_NAMED_CURVE = 1
DH_GENERATOR_2 = 2

View File

@ -74,6 +74,16 @@ if version_info < (1, 1):
void ERR_free_strings(void); void ERR_free_strings(void);
""") """)
if version_info >= (3, ):
ffi.cdef("""
typedef ... OSSL_LIB_CTX;
typedef ... OSSL_PROVIDER;
int OSSL_PROVIDER_available(OSSL_LIB_CTX *libctx, const char *name);
OSSL_PROVIDER *OSSL_PROVIDER_load(OSSL_LIB_CTX *libctx, const char *name);
""")
# The typedef uintptr_t lines here allow us to check for a NULL pointer, # The typedef uintptr_t lines here allow us to check for a NULL pointer,
# without having to redefine the structs in our code. This is kind of a hack, # without having to redefine the structs in our code. This is kind of a hack,
# but it should cause problems since we treat these as opaque. # but it should cause problems since we treat these as opaque.
@ -140,7 +150,6 @@ ffi.cdef("""
EVP_PKEY *X509_get_pubkey(X509 *x); EVP_PKEY *X509_get_pubkey(X509 *x);
void X509_free(X509 *a); void X509_free(X509 *a);
int EVP_PKEY_size(EVP_PKEY *pkey);
RSA *EVP_PKEY_get1_RSA(EVP_PKEY *pkey); RSA *EVP_PKEY_get1_RSA(EVP_PKEY *pkey);
void RSA_free(RSA *r); void RSA_free(RSA *r);
@ -196,6 +205,15 @@ ffi.cdef("""
void EC_KEY_free(EC_KEY *key); void EC_KEY_free(EC_KEY *key);
""") """)
if version_info < (3, ):
ffi.cdef("""
int EVP_PKEY_size(EVP_PKEY *pkey);
""")
else:
ffi.cdef("""
int EVP_PKEY_get_size(EVP_PKEY *pkey);
""")
if version_info < (1, 1): if version_info < (1, 1):
ffi.cdef(""" ffi.cdef("""
EVP_MD_CTX *EVP_MD_CTX_create(void); EVP_MD_CTX *EVP_MD_CTX_create(void);

View File

@ -73,6 +73,8 @@ P_EVP_MD_CTX = c_void_p
P_EVP_MD = c_void_p P_EVP_MD = c_void_p
P_ENGINE = c_void_p P_ENGINE = c_void_p
OSSL_PROVIDER = c_void_p
OSSL_LIB_CTX = c_void_p
P_EVP_PKEY = c_void_p P_EVP_PKEY = c_void_p
EVP_PKEY_CTX = c_void_p EVP_PKEY_CTX = c_void_p
@ -97,6 +99,13 @@ try:
libcrypto.ERR_free_strings.argtypes = [] libcrypto.ERR_free_strings.argtypes = []
libcrypto.ERR_free_strings.restype = None libcrypto.ERR_free_strings.restype = None
if version_info >= (3, ):
libcrypto.OSSL_PROVIDER_available.argtypes = [OSSL_LIB_CTX, c_char_p]
libcrypto.OSSL_PROVIDER_available.restype = c_int
libcrypto.OSSL_PROVIDER_load.argtypes = [OSSL_LIB_CTX, c_char_p]
libcrypto.OSSL_PROVIDER_load.restype = POINTER(OSSL_PROVIDER)
libcrypto.ERR_get_error.argtypes = [] libcrypto.ERR_get_error.argtypes = []
libcrypto.ERR_get_error.restype = c_ulong libcrypto.ERR_get_error.restype = c_ulong
@ -301,10 +310,16 @@ try:
libcrypto.EVP_sha512.argtypes = [] libcrypto.EVP_sha512.argtypes = []
libcrypto.EVP_sha512.restype = P_EVP_MD libcrypto.EVP_sha512.restype = P_EVP_MD
if version_info < (3, 0):
libcrypto.EVP_PKEY_size.argtypes = [ libcrypto.EVP_PKEY_size.argtypes = [
P_EVP_PKEY P_EVP_PKEY
] ]
libcrypto.EVP_PKEY_size.restype = c_int libcrypto.EVP_PKEY_size.restype = c_int
else:
libcrypto.EVP_PKEY_get_size.argtypes = [
P_EVP_PKEY
]
libcrypto.EVP_PKEY_get_size.restype = c_int
libcrypto.EVP_PKEY_get1_RSA.argtypes = [ libcrypto.EVP_PKEY_get1_RSA.argtypes = [
P_EVP_PKEY P_EVP_PKEY

View File

@ -32,6 +32,7 @@ from .._ffi import (
new, new,
null, null,
unwrap, unwrap,
write_to_buffer,
) )
from ._libcrypto import libcrypto, LibcryptoConst, libcrypto_version_info, handle_openssl_error from ._libcrypto import libcrypto, LibcryptoConst, libcrypto_version_info, handle_openssl_error
from ..errors import AsymmetricKeyError, IncompleteAsymmetricKeyError, SignatureError from ..errors import AsymmetricKeyError, IncompleteAsymmetricKeyError, SignatureError
@ -105,6 +106,16 @@ class PrivateKey(_PrivateKeyBase):
pubkey_data = bytes_from_buffer(pubkey_buffer, pubkey_length) pubkey_data = bytes_from_buffer(pubkey_buffer, pubkey_length)
asn1 = PublicKeyInfo.load(pubkey_data) asn1 = PublicKeyInfo.load(pubkey_data)
# OpenSSL 1.x suffers from issues trying to use RSASSA-PSS keys, so we
# masquerade it as a normal RSA key so the OID checks work
if libcrypto_version_info < (3,) and asn1.algorithm == 'rsassa_pss':
temp_asn1 = asn1.copy()
temp_asn1['algorithm']['algorithm'] = 'rsa'
temp_data = temp_asn1.dump()
write_to_buffer(pubkey_buffer, temp_data)
pubkey_length = len(temp_data)
pub_evp_pkey = libcrypto.d2i_PUBKEY(null(), buffer_pointer(pubkey_buffer), pubkey_length) pub_evp_pkey = libcrypto.d2i_PUBKEY(null(), buffer_pointer(pubkey_buffer), pubkey_length)
if is_null(pub_evp_pkey): if is_null(pub_evp_pkey):
handle_openssl_error(0) handle_openssl_error(0)
@ -212,8 +223,13 @@ class Certificate(_CertificateBase):
""" """
if not self._public_key and self.x509: if not self._public_key and self.x509:
# OpenSSL 1.x suffers from issues trying to use RSASSA-PSS keys, so we
# masquerade it as a normal RSA key so the OID checks work
if libcrypto_version_info < (3,) and self.asn1.public_key.algorithm == 'rsassa_pss':
self._public_key = load_public_key(self.asn1.public_key)
else:
evp_pkey = libcrypto.X509_get_pubkey(self.x509) evp_pkey = libcrypto.X509_get_pubkey(self.x509)
self._public_key = PublicKey(evp_pkey, self.asn1['tbs_certificate']['subject_public_key_info']) self._public_key = PublicKey(evp_pkey, self.asn1.public_key)
return self._public_key return self._public_key
@ -233,6 +249,8 @@ class Certificate(_CertificateBase):
if signature_algo == 'rsassa_pkcs1v15': if signature_algo == 'rsassa_pkcs1v15':
verify_func = rsa_pkcs1v15_verify verify_func = rsa_pkcs1v15_verify
elif signature_algo == 'rsassa_pss':
verify_func = rsa_pss_verify
elif signature_algo == 'dsa': elif signature_algo == 'dsa':
verify_func = dsa_verify verify_func = dsa_verify
elif signature_algo == 'ecdsa': elif signature_algo == 'ecdsa':
@ -692,7 +710,7 @@ def load_public_key(source):
source must be a byte string, unicode string or source must be a byte string, unicode string or
asn1crypto.keys.PublicKeyInfo object, not %s asn1crypto.keys.PublicKeyInfo object, not %s
''', ''',
type_name(public_key) type_name(source)
)) ))
if public_key.algorithm == 'dsa': if public_key.algorithm == 'dsa':
@ -712,7 +730,15 @@ def load_public_key(source):
''' '''
)) ))
# OpenSSL 1.x suffers from issues trying to use RSASSA-PSS keys, so we
# masquerade it as a normal RSA key so the OID checks work
if libcrypto_version_info < (3,) and public_key.algorithm == 'rsassa_pss':
temp_key = public_key.copy()
temp_key['algorithm']['algorithm'] = 'rsa'
data = temp_key.dump()
else:
data = public_key.dump() data = public_key.dump()
buffer = buffer_from_bytes(data) buffer = buffer_from_bytes(data)
evp_pkey = libcrypto.d2i_PUBKEY(null(), buffer_pointer(buffer), len(data)) evp_pkey = libcrypto.d2i_PUBKEY(null(), buffer_pointer(buffer), len(data))
if is_null(evp_pkey): if is_null(evp_pkey):
@ -928,6 +954,22 @@ def rsa_oaep_decrypt(private_key, ciphertext):
return _decrypt(private_key, ciphertext, LibcryptoConst.RSA_PKCS1_OAEP_PADDING) return _decrypt(private_key, ciphertext, LibcryptoConst.RSA_PKCS1_OAEP_PADDING)
def _evp_pkey_get_size(evp_pkey):
"""
Handles the function name change from OpenSSL 1.1 -> 3.0
:param evp_pkey:
The EVP_PKEY of the Certificte or PublicKey to get the size of
:return:
An int of the number of bytes necessary for the key
"""
if libcrypto_version_info < (3, ):
return libcrypto.EVP_PKEY_size(evp_pkey)
return libcrypto.EVP_PKEY_get_size(evp_pkey)
def _encrypt(certificate_or_public_key, data, padding): def _encrypt(certificate_or_public_key, data, padding):
""" """
Encrypts plaintext using an RSA public key or certificate Encrypts plaintext using an RSA public key or certificate
@ -970,7 +1012,7 @@ def _encrypt(certificate_or_public_key, data, padding):
rsa = None rsa = None
try: try:
buffer_size = libcrypto.EVP_PKEY_size(certificate_or_public_key.evp_pkey) buffer_size = _evp_pkey_get_size(certificate_or_public_key.evp_pkey)
buffer = buffer_from_bytes(buffer_size) buffer = buffer_from_bytes(buffer_size)
rsa = libcrypto.EVP_PKEY_get1_RSA(certificate_or_public_key.evp_pkey) rsa = libcrypto.EVP_PKEY_get1_RSA(certificate_or_public_key.evp_pkey)
@ -1025,7 +1067,7 @@ def _decrypt(private_key, ciphertext, padding):
rsa = None rsa = None
try: try:
buffer_size = libcrypto.EVP_PKEY_size(private_key.evp_pkey) buffer_size = _evp_pkey_get_size(private_key.evp_pkey)
buffer = buffer_from_bytes(buffer_size) buffer = buffer_from_bytes(buffer_size)
rsa = libcrypto.EVP_PKEY_get1_RSA(private_key.evp_pkey) rsa = libcrypto.EVP_PKEY_get1_RSA(private_key.evp_pkey)
@ -1105,7 +1147,9 @@ def rsa_pss_verify(certificate_or_public_key, signature, data, hash_algorithm):
OSError - when an error is returned by the OS crypto library OSError - when an error is returned by the OS crypto library
""" """
if certificate_or_public_key.algorithm != 'rsa': cp_alg = certificate_or_public_key.algorithm
if cp_alg != 'rsa' and cp_alg != 'rsassa_pss':
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
The key specified is not an RSA public key, but %s The key specified is not an RSA public key, but %s
@ -1235,13 +1279,16 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
type_name(data) type_name(data)
)) ))
cp_alg = certificate_or_public_key.algorithm
cp_is_rsa = cp_alg == 'rsa' or cp_alg == 'rsassa_pss'
valid_hash_algorithms = set(['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']) valid_hash_algorithms = set(['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'])
if certificate_or_public_key.algorithm == 'rsa' and not rsa_pss_padding: if cp_is_rsa and not rsa_pss_padding:
valid_hash_algorithms |= set(['raw']) valid_hash_algorithms |= set(['raw'])
if hash_algorithm not in valid_hash_algorithms: if hash_algorithm not in valid_hash_algorithms:
valid_hash_algorithms_error = '"md5", "sha1", "sha224", "sha256", "sha384", "sha512"' valid_hash_algorithms_error = '"md5", "sha1", "sha224", "sha256", "sha384", "sha512"'
if certificate_or_public_key.algorithm == 'rsa' and not rsa_pss_padding: if cp_is_rsa and not rsa_pss_padding:
valid_hash_algorithms_error += ', "raw"' valid_hash_algorithms_error += ', "raw"'
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
@ -1251,16 +1298,16 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
repr(hash_algorithm) repr(hash_algorithm)
)) ))
if certificate_or_public_key.algorithm != 'rsa' and rsa_pss_padding: if not cp_is_rsa and rsa_pss_padding:
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
PSS padding can only be used with RSA keys - the key provided is a PSS padding can only be used with RSA keys - the key provided is a
%s key %s key
''', ''',
certificate_or_public_key.algorithm.upper() cp_alg.upper()
)) ))
if certificate_or_public_key.algorithm == 'rsa' and hash_algorithm == 'raw': if cp_is_rsa and hash_algorithm == 'raw':
if len(data) > certificate_or_public_key.byte_size - 11: if len(data) > certificate_or_public_key.byte_size - 11:
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
@ -1279,7 +1326,7 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
if is_null(rsa): if is_null(rsa):
handle_openssl_error(0) handle_openssl_error(0)
buffer_size = libcrypto.EVP_PKEY_size(certificate_or_public_key.evp_pkey) buffer_size = _evp_pkey_get_size(certificate_or_public_key.evp_pkey)
decrypted_buffer = buffer_from_bytes(buffer_size) decrypted_buffer = buffer_from_bytes(buffer_size)
decrypted_length = libcrypto.RSA_public_decrypt( decrypted_length = libcrypto.RSA_public_decrypt(
len(signature), len(signature),
@ -1323,14 +1370,14 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
}[hash_algorithm]() }[hash_algorithm]()
if libcrypto_version_info < (1,): if libcrypto_version_info < (1,):
if certificate_or_public_key.algorithm == 'rsa' and rsa_pss_padding: if cp_is_rsa and rsa_pss_padding:
digest = getattr(hashlib, hash_algorithm)(data).digest() digest = getattr(hashlib, hash_algorithm)(data).digest()
rsa = libcrypto.EVP_PKEY_get1_RSA(certificate_or_public_key.evp_pkey) rsa = libcrypto.EVP_PKEY_get1_RSA(certificate_or_public_key.evp_pkey)
if is_null(rsa): if is_null(rsa):
handle_openssl_error(0) handle_openssl_error(0)
buffer_size = libcrypto.EVP_PKEY_size(certificate_or_public_key.evp_pkey) buffer_size = _evp_pkey_get_size(certificate_or_public_key.evp_pkey)
decoded_buffer = buffer_from_bytes(buffer_size) decoded_buffer = buffer_from_bytes(buffer_size)
decoded_length = libcrypto.RSA_public_decrypt( decoded_length = libcrypto.RSA_public_decrypt(
len(signature), len(signature),
@ -1349,7 +1396,7 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
LibcryptoConst.EVP_MD_CTX_FLAG_PSS_MDLEN LibcryptoConst.EVP_MD_CTX_FLAG_PSS_MDLEN
) )
elif certificate_or_public_key.algorithm == 'rsa': elif cp_is_rsa:
res = libcrypto.EVP_DigestInit_ex(evp_md_ctx, evp_md, null()) res = libcrypto.EVP_DigestInit_ex(evp_md_ctx, evp_md, null())
handle_openssl_error(res) handle_openssl_error(res)
@ -1363,7 +1410,7 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
certificate_or_public_key.evp_pkey certificate_or_public_key.evp_pkey
) )
elif certificate_or_public_key.algorithm == 'dsa': elif cp_alg == 'dsa':
digest = getattr(hashlib, hash_algorithm)(data).digest() digest = getattr(hashlib, hash_algorithm)(data).digest()
signature_buffer = buffer_from_bytes(signature) signature_buffer = buffer_from_bytes(signature)
@ -1378,7 +1425,7 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
res = libcrypto.DSA_do_verify(digest, len(digest), dsa_sig, dsa) res = libcrypto.DSA_do_verify(digest, len(digest), dsa_sig, dsa)
elif certificate_or_public_key.algorithm == 'ec': elif cp_alg == 'ec':
digest = getattr(hashlib, hash_algorithm)(data).digest() digest = getattr(hashlib, hash_algorithm)(data).digest()
signature_buffer = buffer_from_bytes(signature) signature_buffer = buffer_from_bytes(signature)
@ -1418,6 +1465,7 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
handle_openssl_error(res) handle_openssl_error(res)
# Use the hash algorithm output length as the salt length # Use the hash algorithm output length as the salt length
if libcrypto_version_info < (3, 0):
res = libcrypto.EVP_PKEY_CTX_ctrl( res = libcrypto.EVP_PKEY_CTX_ctrl(
evp_pkey_ctx_pointer, evp_pkey_ctx_pointer,
LibcryptoConst.EVP_PKEY_RSA, LibcryptoConst.EVP_PKEY_RSA,
@ -1519,12 +1567,14 @@ def rsa_pss_sign(private_key, data, hash_algorithm):
A byte string of the signature A byte string of the signature
""" """
if private_key.algorithm != 'rsa': pkey_alg = private_key.algorithm
if pkey_alg != 'rsa' and pkey_alg != 'rsassa_pss':
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
The key specified is not an RSA private key, but %s The key specified is not an RSA private key, but %s
''', ''',
private_key.algorithm.upper() pkey_alg.upper()
)) ))
return _sign(private_key, data, hash_algorithm, rsa_pss_padding=True) return _sign(private_key, data, hash_algorithm, rsa_pss_padding=True)
@ -1637,13 +1687,16 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
type_name(data) type_name(data)
)) ))
pkey_alg = private_key.algorithm
pkey_is_rsa = pkey_alg == 'rsa' or pkey_alg == 'rsassa_pss'
valid_hash_algorithms = set(['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']) valid_hash_algorithms = set(['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'])
if private_key.algorithm == 'rsa' and not rsa_pss_padding: if pkey_alg == 'rsa' and not rsa_pss_padding:
valid_hash_algorithms |= set(['raw']) valid_hash_algorithms |= set(['raw'])
if hash_algorithm not in valid_hash_algorithms: if hash_algorithm not in valid_hash_algorithms:
valid_hash_algorithms_error = '"md5", "sha1", "sha224", "sha256", "sha384", "sha512"' valid_hash_algorithms_error = '"md5", "sha1", "sha224", "sha256", "sha384", "sha512"'
if private_key.algorithm == 'rsa' and not rsa_pss_padding: if pkey_is_rsa and not rsa_pss_padding:
valid_hash_algorithms_error += ', "raw"' valid_hash_algorithms_error += ', "raw"'
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
@ -1653,16 +1706,16 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
repr(hash_algorithm) repr(hash_algorithm)
)) ))
if private_key.algorithm != 'rsa' and rsa_pss_padding: if not pkey_is_rsa and rsa_pss_padding:
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
PSS padding can only be used with RSA keys - the key provided is a PSS padding can only be used with RSA keys - the key provided is a
%s key %s key
''', ''',
private_key.algorithm.upper() pkey_alg.upper()
)) ))
if private_key.algorithm == 'rsa' and hash_algorithm == 'raw': if pkey_is_rsa and hash_algorithm == 'raw':
if len(data) > private_key.byte_size - 11: if len(data) > private_key.byte_size - 11:
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
@ -1681,7 +1734,7 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
if is_null(rsa): if is_null(rsa):
handle_openssl_error(0) handle_openssl_error(0)
buffer_size = libcrypto.EVP_PKEY_size(private_key.evp_pkey) buffer_size = _evp_pkey_get_size(private_key.evp_pkey)
signature_buffer = buffer_from_bytes(buffer_size) signature_buffer = buffer_from_bytes(buffer_size)
signature_length = libcrypto.RSA_private_encrypt( signature_length = libcrypto.RSA_private_encrypt(
@ -1722,14 +1775,14 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
}[hash_algorithm]() }[hash_algorithm]()
if libcrypto_version_info < (1,): if libcrypto_version_info < (1,):
if private_key.algorithm == 'rsa' and rsa_pss_padding: if pkey_is_rsa and rsa_pss_padding:
digest = getattr(hashlib, hash_algorithm)(data).digest() digest = getattr(hashlib, hash_algorithm)(data).digest()
rsa = libcrypto.EVP_PKEY_get1_RSA(private_key.evp_pkey) rsa = libcrypto.EVP_PKEY_get1_RSA(private_key.evp_pkey)
if is_null(rsa): if is_null(rsa):
handle_openssl_error(0) handle_openssl_error(0)
buffer_size = libcrypto.EVP_PKEY_size(private_key.evp_pkey) buffer_size = _evp_pkey_get_size(private_key.evp_pkey)
em_buffer = buffer_from_bytes(buffer_size) em_buffer = buffer_from_bytes(buffer_size)
res = libcrypto.RSA_padding_add_PKCS1_PSS( res = libcrypto.RSA_padding_add_PKCS1_PSS(
rsa, rsa,
@ -1750,8 +1803,8 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
) )
handle_openssl_error(signature_length) handle_openssl_error(signature_length)
elif private_key.algorithm == 'rsa': elif pkey_is_rsa:
buffer_size = libcrypto.EVP_PKEY_size(private_key.evp_pkey) buffer_size = _evp_pkey_get_size(private_key.evp_pkey)
signature_buffer = buffer_from_bytes(buffer_size) signature_buffer = buffer_from_bytes(buffer_size)
signature_length = new(libcrypto, 'unsigned int *') signature_length = new(libcrypto, 'unsigned int *')
@ -1771,7 +1824,7 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
signature_length = deref(signature_length) signature_length = deref(signature_length)
elif private_key.algorithm == 'dsa': elif pkey_alg == 'dsa':
digest = getattr(hashlib, hash_algorithm)(data).digest() digest = getattr(hashlib, hash_algorithm)(data).digest()
dsa = libcrypto.EVP_PKEY_get1_DSA(private_key.evp_pkey) dsa = libcrypto.EVP_PKEY_get1_DSA(private_key.evp_pkey)
@ -1788,7 +1841,7 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
signature_length = libcrypto.i2d_DSA_SIG(dsa_sig, signature_pointer) signature_length = libcrypto.i2d_DSA_SIG(dsa_sig, signature_pointer)
handle_openssl_error(signature_length) handle_openssl_error(signature_length)
elif private_key.algorithm == 'ec': elif pkey_alg == 'ec':
digest = getattr(hashlib, hash_algorithm)(data).digest() digest = getattr(hashlib, hash_algorithm)(data).digest()
ec_key = libcrypto.EVP_PKEY_get1_EC_KEY(private_key.evp_pkey) ec_key = libcrypto.EVP_PKEY_get1_EC_KEY(private_key.evp_pkey)
@ -1806,7 +1859,7 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
handle_openssl_error(signature_length) handle_openssl_error(signature_length)
else: else:
buffer_size = libcrypto.EVP_PKEY_size(private_key.evp_pkey) buffer_size = _evp_pkey_get_size(private_key.evp_pkey)
signature_buffer = buffer_from_bytes(buffer_size) signature_buffer = buffer_from_bytes(buffer_size)
signature_length = new(libcrypto, 'size_t *', buffer_size) signature_length = new(libcrypto, 'size_t *', buffer_size)
@ -1834,6 +1887,7 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
handle_openssl_error(res) handle_openssl_error(res)
# Use the hash algorithm output length as the salt length # Use the hash algorithm output length as the salt length
if libcrypto_version_info < (3, 0):
res = libcrypto.EVP_PKEY_CTX_ctrl( res = libcrypto.EVP_PKEY_CTX_ctrl(
evp_pkey_ctx_pointer, evp_pkey_ctx_pointer,
LibcryptoConst.EVP_PKEY_RSA, LibcryptoConst.EVP_PKEY_RSA,

View File

@ -5,7 +5,7 @@ import math
from .._errors import pretty_message from .._errors import pretty_message
from .._ffi import new, null, is_null, buffer_from_bytes, bytes_from_buffer, deref from .._ffi import new, null, is_null, buffer_from_bytes, bytes_from_buffer, deref
from ._libcrypto import libcrypto, LibcryptoConst, handle_openssl_error from ._libcrypto import libcrypto, libcrypto_legacy_support, LibcryptoConst, handle_openssl_error
from ..util import rand_bytes from ..util import rand_bytes
from .._types import type_name, byte_cls from .._types import type_name, byte_cls
@ -236,6 +236,9 @@ def rc4_encrypt(key, data):
A byte string of the ciphertext A byte string of the ciphertext
""" """
if not libcrypto_legacy_support:
raise EnvironmentError('OpenSSL has been compiled without RC4 support')
if len(key) < 5 or len(key) > 16: if len(key) < 5 or len(key) > 16:
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
@ -266,6 +269,9 @@ def rc4_decrypt(key, data):
A byte string of the plaintext A byte string of the plaintext
""" """
if not libcrypto_legacy_support:
raise EnvironmentError('OpenSSL has been compiled without RC4 support')
if len(key) < 5 or len(key) > 16: if len(key) < 5 or len(key) > 16:
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
@ -301,6 +307,9 @@ def rc2_cbc_pkcs5_encrypt(key, data, iv):
A tuple of two byte strings (iv, ciphertext) A tuple of two byte strings (iv, ciphertext)
""" """
if not libcrypto_legacy_support:
raise EnvironmentError('OpenSSL has been compiled without RC2 support')
if len(key) < 5 or len(key) > 16: if len(key) < 5 or len(key) > 16:
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
@ -345,6 +354,9 @@ def rc2_cbc_pkcs5_decrypt(key, data, iv):
A byte string of the plaintext A byte string of the plaintext
""" """
if not libcrypto_legacy_support:
raise EnvironmentError('OpenSSL has been compiled without RC2 support')
if len(key) < 5 or len(key) > 16: if len(key) < 5 or len(key) > 16:
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
@ -487,6 +499,9 @@ def des_cbc_pkcs5_encrypt(key, data, iv):
A tuple of two byte strings (iv, ciphertext) A tuple of two byte strings (iv, ciphertext)
""" """
if not libcrypto_legacy_support:
raise EnvironmentError('OpenSSL has been compiled without DES support')
if len(key) != 8: if len(key) != 8:
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
@ -530,6 +545,9 @@ def des_cbc_pkcs5_decrypt(key, data, iv):
A byte string of the plaintext A byte string of the plaintext
""" """
if not libcrypto_legacy_support:
raise EnvironmentError('OpenSSL has been compiled without DES support')
if len(key) != 8: if len(key) != 8:
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
@ -604,23 +622,9 @@ def _encrypt(cipher, key, data, iv, padding):
if cipher != 'rc4' and not padding: if cipher != 'rc4' and not padding:
# AES in CBC mode can be allowed with no padding if # AES in CBC mode can be allowed with no padding if
# the data is an exact multiple of the key size # the data is an exact multiple of the block size
aes128_no_padding = ( is_aes = cipher in set(['aes128', 'aes192', 'aes256'])
cipher == 'aes128' and if not is_aes or (is_aes and (len(data) % 16) != 0):
padding is False and
len(data) % 16 == 0
)
aes192_no_padding = (
cipher == 'aes192' and
padding is False and
len(data) % 24 == 0
)
aes256_no_padding = (
cipher == 'aes256' and
padding is False and
len(data) % 32 == 0
)
if aes128_no_padding is False and aes192_no_padding is False and aes256_no_padding is False:
raise ValueError('padding must be specified') raise ValueError('padding must be specified')
evp_cipher_ctx = None evp_cipher_ctx = None
@ -730,7 +734,7 @@ def _decrypt(cipher, key, data, iv, padding):
type_name(iv) type_name(iv)
)) ))
if cipher != 'rc4' and padding is None: if cipher not in set(['rc4', 'aes128', 'aes192', 'aes256']) and not padding:
raise ValueError('padding must be specified') raise ValueError('padding must be specified')
evp_cipher_ctx = None evp_cipher_ctx = None

View File

@ -65,6 +65,25 @@ _PROTOCOL_MAP = {
} }
def _homogenize_openssl3_error(error_tuple):
"""
Takes a 3-element tuple from peek_openssl_error() and modifies it
to handle the changes in OpenSSL 3.0. That release removed the
concept of an error function, meaning the second item in the tuple
will always be 0.
:param error_tuple:
A 3-element tuple of integers
:return:
A 3-element tuple of integers
"""
if libcrypto_version_info < (3,):
return error_tuple
return (error_tuple[0], 0, error_tuple[2])
class TLSSession(object): class TLSSession(object):
""" """
A TLS session object that multiple TLSSocket objects can share for the A TLS session object that multiple TLSSocket objects can share for the
@ -372,7 +391,7 @@ class TLSSocket(object):
def __init__(self, address, port, timeout=10, session=None): def __init__(self, address, port, timeout=10, session=None):
""" """
:param address: :param address:
A unicode string of the domain name or IP address to conenct to A unicode string of the domain name or IP address to connect to
:param port: :param port:
An integer of the port number to connect to An integer of the port number to connect to
@ -516,16 +535,22 @@ class TLSSocket(object):
LibsslConst.SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM, LibsslConst.SSL_F_SSL3_CHECK_CERT_AND_ALGORITHM,
LibsslConst.SSL_R_DH_KEY_TOO_SMALL LibsslConst.SSL_R_DH_KEY_TOO_SMALL
) )
dh_key_info_1 = _homogenize_openssl3_error(dh_key_info_1)
dh_key_info_2 = ( dh_key_info_2 = (
LibsslConst.ERR_LIB_SSL, LibsslConst.ERR_LIB_SSL,
LibsslConst.SSL_F_TLS_PROCESS_SKE_DHE, LibsslConst.SSL_F_TLS_PROCESS_SKE_DHE,
LibsslConst.SSL_R_DH_KEY_TOO_SMALL LibsslConst.SSL_R_DH_KEY_TOO_SMALL
) )
dh_key_info_2 = _homogenize_openssl3_error(dh_key_info_2)
dh_key_info_3 = ( dh_key_info_3 = (
LibsslConst.ERR_LIB_SSL, LibsslConst.ERR_LIB_SSL,
LibsslConst.SSL_F_SSL3_GET_KEY_EXCHANGE, LibsslConst.SSL_F_SSL3_GET_KEY_EXCHANGE,
LibsslConst.SSL_R_BAD_DH_P_LENGTH LibsslConst.SSL_R_BAD_DH_P_LENGTH
) )
dh_key_info_3 = _homogenize_openssl3_error(dh_key_info_3)
if info == dh_key_info_1 or info == dh_key_info_2 or info == dh_key_info_3: if info == dh_key_info_1 or info == dh_key_info_2 or info == dh_key_info_3:
raise_dh_params() raise_dh_params()
@ -541,6 +566,8 @@ class TLSSocket(object):
LibsslConst.SSL_F_SSL3_GET_RECORD, LibsslConst.SSL_F_SSL3_GET_RECORD,
LibsslConst.SSL_R_WRONG_VERSION_NUMBER LibsslConst.SSL_R_WRONG_VERSION_NUMBER
) )
unknown_protocol_info = _homogenize_openssl3_error(unknown_protocol_info)
if info == unknown_protocol_info: if info == unknown_protocol_info:
raise_protocol_error(handshake_server_bytes) raise_protocol_error(handshake_server_bytes)
@ -549,6 +576,7 @@ class TLSSocket(object):
LibsslConst.SSL_F_SSL23_GET_SERVER_HELLO, LibsslConst.SSL_F_SSL23_GET_SERVER_HELLO,
LibsslConst.SSL_R_TLSV1_ALERT_PROTOCOL_VERSION LibsslConst.SSL_R_TLSV1_ALERT_PROTOCOL_VERSION
) )
tls_version_info_error = _homogenize_openssl3_error(tls_version_info_error)
if info == tls_version_info_error: if info == tls_version_info_error:
raise_protocol_version() raise_protocol_version()
@ -557,7 +585,9 @@ class TLSSocket(object):
LibsslConst.SSL_F_SSL23_GET_SERVER_HELLO, LibsslConst.SSL_F_SSL23_GET_SERVER_HELLO,
LibsslConst.SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE LibsslConst.SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE
) )
if info == handshake_error_info: # OpenSSL 3.0 no longer has func codes, so this can be confused
# with the following handler which needs to check for client auth
if libcrypto_version_info < (3, ) and info == handshake_error_info:
raise_handshake() raise_handshake()
handshake_failure_info = ( handshake_failure_info = (
@ -565,6 +595,7 @@ class TLSSocket(object):
LibsslConst.SSL_F_SSL3_READ_BYTES, LibsslConst.SSL_F_SSL3_READ_BYTES,
LibsslConst.SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE LibsslConst.SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE
) )
handshake_failure_info = _homogenize_openssl3_error(handshake_failure_info)
if info == handshake_failure_info: if info == handshake_failure_info:
saw_client_auth = False saw_client_auth = False
for record_type, _, record_data in parse_tls_records(handshake_server_bytes): for record_type, _, record_data in parse_tls_records(handshake_server_bytes):
@ -590,6 +621,7 @@ class TLSSocket(object):
LibsslConst.SSL_F_TLS_PROCESS_SERVER_CERTIFICATE, LibsslConst.SSL_F_TLS_PROCESS_SERVER_CERTIFICATE,
LibsslConst.SSL_R_CERTIFICATE_VERIFY_FAILED LibsslConst.SSL_R_CERTIFICATE_VERIFY_FAILED
) )
cert_verify_failed_info = _homogenize_openssl3_error(cert_verify_failed_info)
# It would appear that some versions of OpenSSL (such as on Fedora 30) # It would appear that some versions of OpenSSL (such as on Fedora 30)
# don't even have the MD5 digest algorithm included any longer? To # don't even have the MD5 digest algorithm included any longer? To
@ -599,6 +631,7 @@ class TLSSocket(object):
LibsslConst.ASN1_F_ASN1_ITEM_VERIFY, LibsslConst.ASN1_F_ASN1_ITEM_VERIFY,
LibsslConst.ASN1_R_UNKNOWN_MESSAGE_DIGEST_ALGORITHM LibsslConst.ASN1_R_UNKNOWN_MESSAGE_DIGEST_ALGORITHM
) )
unknown_hash_algo_info = _homogenize_openssl3_error(unknown_hash_algo_info)
if info == unknown_hash_algo_info: if info == unknown_hash_algo_info:
chain = extract_chain(handshake_server_bytes) chain = extract_chain(handshake_server_bytes)

View File

@ -651,7 +651,7 @@ def raw_rsa_private_crypt(private_key, data):
)) ))
algo = private_key.asn1['private_key_algorithm']['algorithm'].native algo = private_key.asn1['private_key_algorithm']['algorithm'].native
if algo != 'rsa': if algo != 'rsa' and algo != 'rsassa_pss':
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
private_key must be an RSA key, not %s private_key must be an RSA key, not %s
@ -712,7 +712,7 @@ def raw_rsa_public_crypt(certificate_or_public_key, data):
)) ))
algo = certificate_or_public_key.asn1['algorithm']['algorithm'].native algo = certificate_or_public_key.asn1['algorithm']['algorithm'].native
if algo != 'rsa': if algo != 'rsa' and algo != 'rsassa_pss':
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
certificate_or_public_key must be an RSA key, not %s certificate_or_public_key must be an RSA key, not %s

View File

@ -190,7 +190,7 @@ def pkcs12_kdf(hash_algorithm, password, salt, iterations, key_length, id_):
i = i[0:start] + i_num2 + i[end:] i = i[0:start] + i_num2 + i[end:]
# Step 7 (one peice at a time) # Step 7 (one piece at a time)
begin = (num - 1) * u begin = (num - 1) * u
to_copy = min(key_length, u) to_copy = min(key_length, u)
a = a[0:begin] + a2[0:to_copy] + a[begin + to_copy:] a = a[0:begin] + a2[0:to_copy] + a[begin + to_copy:]

View File

@ -465,6 +465,22 @@ def raise_self_signed(certificate):
raise TLSVerificationError(message, certificate) raise TLSVerificationError(message, certificate)
def raise_lifetime_too_long(certificate):
"""
Raises a TLSVerificationError due to a certificate lifetime exceeding
the CAB forum certificate lifetime limit
:param certificate:
An asn1crypto.x509.Certificate object
:raises:
TLSVerificationError
"""
message = 'Server certificate verification failed - certificate lifetime is too long'
raise TLSVerificationError(message, certificate)
def raise_expired_not_yet_valid(certificate): def raise_expired_not_yet_valid(certificate):
""" """
Raises a TLSVerificationError due to certificate being expired, or not yet Raises a TLSVerificationError due to certificate being expired, or not yet

View File

@ -34,7 +34,7 @@ def open_context_handle(provider, verify_only=True):
else: else:
raise ValueError('Invalid provider specified: %s' % provider) raise ValueError('Invalid provider specified: %s' % provider)
# Ths DSS provider needs a container to allow importing and exporting # The DSS provider needs a container to allow importing and exporting
# private keys, but all of the RSA stuff works fine with CRYPT_VERIFYCONTEXT # private keys, but all of the RSA stuff works fine with CRYPT_VERIFYCONTEXT
if verify_only or provider != Advapi32Const.MS_ENH_DSS_DH_PROV: if verify_only or provider != Advapi32Const.MS_ENH_DSS_DH_PROV:
container_name = null() container_name = null()

View File

@ -567,6 +567,8 @@ class Certificate(_WinKey, _CertificateBase):
if signature_algo == 'rsassa_pkcs1v15': if signature_algo == 'rsassa_pkcs1v15':
verify_func = rsa_pkcs1v15_verify verify_func = rsa_pkcs1v15_verify
elif signature_algo == 'rsassa_pss':
verify_func = rsa_pss_verify
elif signature_algo == 'dsa': elif signature_algo == 'dsa':
verify_func = dsa_verify verify_func = dsa_verify
elif signature_algo == 'ecdsa': elif signature_algo == 'ecdsa':
@ -1650,8 +1652,10 @@ def _advapi32_load_key(key_object, key_info, container):
key_type = 'public' if isinstance(key_info, PublicKeyInfo) else 'private' key_type = 'public' if isinstance(key_info, PublicKeyInfo) else 'private'
algo = key_info.algorithm algo = key_info.algorithm
if algo == 'rsassa_pss':
algo = 'rsa'
if algo == 'rsa': if algo == 'rsa' or algo == 'rsassa_pss':
provider = Advapi32Const.MS_ENH_RSA_AES_PROV provider = Advapi32Const.MS_ENH_RSA_AES_PROV
else: else:
provider = Advapi32Const.MS_ENH_DSS_DH_PROV provider = Advapi32Const.MS_ENH_DSS_DH_PROV
@ -1844,6 +1848,8 @@ def _bcrypt_load_key(key_object, key_info, container, curve_name):
key_type = 'public' if isinstance(key_info, PublicKeyInfo) else 'private' key_type = 'public' if isinstance(key_info, PublicKeyInfo) else 'private'
algo = key_info.algorithm algo = key_info.algorithm
if algo == 'rsassa_pss':
algo = 'rsa'
try: try:
alg_selector = key_info.curve[1] if algo == 'ec' else algo alg_selector = key_info.curve[1] if algo == 'ec' else algo
@ -2282,7 +2288,9 @@ def rsa_pss_verify(certificate_or_public_key, signature, data, hash_algorithm):
OSError - when an error is returned by the OS crypto library OSError - when an error is returned by the OS crypto library
""" """
if certificate_or_public_key.algorithm != 'rsa': cp_alg = certificate_or_public_key.algorithm
if cp_alg != 'rsa' and cp_alg != 'rsassa_pss':
raise ValueError('The key specified is not an RSA public key') raise ValueError('The key specified is not an RSA public key')
return _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_padding=True) return _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_padding=True)
@ -2397,13 +2405,16 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
type_name(data) type_name(data)
)) ))
cp_alg = certificate_or_public_key.algorithm
cp_is_rsa = cp_alg == 'rsa' or cp_alg == 'rsassa_pss'
valid_hash_algorithms = set(['md5', 'sha1', 'sha256', 'sha384', 'sha512']) valid_hash_algorithms = set(['md5', 'sha1', 'sha256', 'sha384', 'sha512'])
if certificate_or_public_key.algorithm == 'rsa' and not rsa_pss_padding: if cp_is_rsa and not rsa_pss_padding:
valid_hash_algorithms |= set(['raw']) valid_hash_algorithms |= set(['raw'])
if hash_algorithm not in valid_hash_algorithms: if hash_algorithm not in valid_hash_algorithms:
valid_hash_algorithms_error = '"md5", "sha1", "sha256", "sha384", "sha512"' valid_hash_algorithms_error = '"md5", "sha1", "sha256", "sha384", "sha512"'
if certificate_or_public_key.algorithm == 'rsa' and not rsa_pss_padding: if cp_is_rsa and not rsa_pss_padding:
valid_hash_algorithms_error += ', "raw"' valid_hash_algorithms_error += ', "raw"'
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
@ -2413,13 +2424,13 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
repr(hash_algorithm) repr(hash_algorithm)
)) ))
if certificate_or_public_key.algorithm != 'rsa' and rsa_pss_padding is not False: if not cp_is_rsa and rsa_pss_padding is not False:
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
PSS padding may only be used with RSA keys - signing via a %s key PSS padding may only be used with RSA keys - signing via a %s key
was requested was requested
''', ''',
certificate_or_public_key.algorithm.upper() cp_alg.upper()
)) ))
if hash_algorithm == 'raw': if hash_algorithm == 'raw':
@ -2468,8 +2479,9 @@ def _advapi32_verify(certificate_or_public_key, signature, data, hash_algorithm,
""" """
algo = certificate_or_public_key.algorithm algo = certificate_or_public_key.algorithm
algo_is_rsa = algo == 'rsa' or algo == 'rsassa_pss'
if algo == 'rsa' and rsa_pss_padding: if algo_is_rsa and rsa_pss_padding:
hash_length = { hash_length = {
'sha1': 20, 'sha1': 20,
'sha224': 28, 'sha224': 28,
@ -2483,7 +2495,7 @@ def _advapi32_verify(certificate_or_public_key, signature, data, hash_algorithm,
raise SignatureError('Signature is invalid') raise SignatureError('Signature is invalid')
return return
if algo == 'rsa' and hash_algorithm == 'raw': if algo_is_rsa and hash_algorithm == 'raw':
padded_plaintext = raw_rsa_public_crypt(certificate_or_public_key, signature) padded_plaintext = raw_rsa_public_crypt(certificate_or_public_key, signature)
try: try:
plaintext = remove_pkcs1v15_signature_padding(certificate_or_public_key.byte_size, padded_plaintext) plaintext = remove_pkcs1v15_signature_padding(certificate_or_public_key.byte_size, padded_plaintext)
@ -2591,7 +2603,10 @@ def _bcrypt_verify(certificate_or_public_key, signature, data, hash_algorithm, r
padding_info = null() padding_info = null()
flags = 0 flags = 0
if certificate_or_public_key.algorithm == 'rsa': cp_alg = certificate_or_public_key.algorithm
cp_is_rsa = cp_alg == 'rsa' or cp_alg == 'rsassa_pss'
if cp_is_rsa:
if rsa_pss_padding: if rsa_pss_padding:
flags = BcryptConst.BCRYPT_PAD_PSS flags = BcryptConst.BCRYPT_PAD_PSS
padding_info_struct_pointer = struct(bcrypt, 'BCRYPT_PSS_PADDING_INFO') padding_info_struct_pointer = struct(bcrypt, 'BCRYPT_PSS_PADDING_INFO')
@ -2694,7 +2709,9 @@ def rsa_pss_sign(private_key, data, hash_algorithm):
A byte string of the signature A byte string of the signature
""" """
if private_key.algorithm != 'rsa': pkey_alg = private_key.algorithm
if pkey_alg != 'rsa' and pkey_alg != 'rsassa_pss':
raise ValueError('The key specified is not an RSA private key') raise ValueError('The key specified is not an RSA private key')
return _sign(private_key, data, hash_algorithm, rsa_pss_padding=True) return _sign(private_key, data, hash_algorithm, rsa_pss_padding=True)
@ -2797,13 +2814,16 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
type_name(data) type_name(data)
)) ))
pkey_alg = private_key.algorithm
pkey_is_rsa = pkey_alg == 'rsa' or pkey_alg == 'rsassa_pss'
valid_hash_algorithms = set(['md5', 'sha1', 'sha256', 'sha384', 'sha512']) valid_hash_algorithms = set(['md5', 'sha1', 'sha256', 'sha384', 'sha512'])
if private_key.algorithm == 'rsa' and not rsa_pss_padding: if private_key.algorithm == 'rsa' and not rsa_pss_padding:
valid_hash_algorithms |= set(['raw']) valid_hash_algorithms |= set(['raw'])
if hash_algorithm not in valid_hash_algorithms: if hash_algorithm not in valid_hash_algorithms:
valid_hash_algorithms_error = '"md5", "sha1", "sha256", "sha384", "sha512"' valid_hash_algorithms_error = '"md5", "sha1", "sha256", "sha384", "sha512"'
if private_key.algorithm == 'rsa' and not rsa_pss_padding: if pkey_is_rsa and not rsa_pss_padding:
valid_hash_algorithms_error += ', "raw"' valid_hash_algorithms_error += ', "raw"'
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
@ -2813,13 +2833,13 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
repr(hash_algorithm) repr(hash_algorithm)
)) ))
if private_key.algorithm != 'rsa' and rsa_pss_padding is not False: if not pkey_is_rsa and rsa_pss_padding is not False:
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
PSS padding may only be used with RSA keys - signing via a %s key PSS padding may only be used with RSA keys - signing via a %s key
was requested was requested
''', ''',
private_key.algorithm.upper() pkey_alg.upper()
)) ))
if hash_algorithm == 'raw': if hash_algorithm == 'raw':
@ -2867,12 +2887,13 @@ def _advapi32_sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
""" """
algo = private_key.algorithm algo = private_key.algorithm
algo_is_rsa = algo == 'rsa' or algo == 'rsassa_pss'
if algo == 'rsa' and hash_algorithm == 'raw': if algo_is_rsa and hash_algorithm == 'raw':
padded_data = add_pkcs1v15_signature_padding(private_key.byte_size, data) padded_data = add_pkcs1v15_signature_padding(private_key.byte_size, data)
return raw_rsa_private_crypt(private_key, padded_data) return raw_rsa_private_crypt(private_key, padded_data)
if algo == 'rsa' and rsa_pss_padding: if algo_is_rsa and rsa_pss_padding:
hash_length = { hash_length = {
'sha1': 20, 'sha1': 20,
'sha224': 28, 'sha224': 28,
@ -3003,7 +3024,10 @@ def _bcrypt_sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
padding_info = null() padding_info = null()
flags = 0 flags = 0
if private_key.algorithm == 'rsa': pkey_alg = private_key.algorithm
pkey_is_rsa = pkey_alg == 'rsa' or pkey_alg == 'rsassa_pss'
if pkey_is_rsa:
if rsa_pss_padding: if rsa_pss_padding:
hash_length = { hash_length = {
'md5': 16, 'md5': 16,
@ -3032,7 +3056,7 @@ def _bcrypt_sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
padding_info_struct.pszAlgId = cast(bcrypt, 'wchar_t *', hash_buffer) padding_info_struct.pszAlgId = cast(bcrypt, 'wchar_t *', hash_buffer)
padding_info = cast(bcrypt, 'void *', padding_info_struct_pointer) padding_info = cast(bcrypt, 'void *', padding_info_struct_pointer)
if private_key.algorithm == 'dsa' and private_key.bit_size > 1024 and hash_algorithm in set(['md5', 'sha1']): if pkey_alg == 'dsa' and private_key.bit_size > 1024 and hash_algorithm in set(['md5', 'sha1']):
raise ValueError(pretty_message( raise ValueError(pretty_message(
''' '''
Windows does not support sha1 signatures with DSA keys based on Windows does not support sha1 signatures with DSA keys based on
@ -3056,7 +3080,7 @@ def _bcrypt_sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
buffer_len = deref(out_len) buffer_len = deref(out_len)
buffer = buffer_from_bytes(buffer_len) buffer = buffer_from_bytes(buffer_len)
if private_key.algorithm == 'rsa': if pkey_is_rsa:
padding_info = cast(bcrypt, 'void *', padding_info_struct_pointer) padding_info = cast(bcrypt, 'void *', padding_info_struct_pointer)
res = bcrypt.BCryptSignHash( res = bcrypt.BCryptSignHash(
@ -3072,7 +3096,7 @@ def _bcrypt_sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
handle_error(res) handle_error(res)
signature = bytes_from_buffer(buffer, deref(out_len)) signature = bytes_from_buffer(buffer, deref(out_len))
if private_key.algorithm != 'rsa': if not pkey_is_rsa:
# Windows doesn't use the ASN.1 Sequence for DSA/ECDSA signatures, # Windows doesn't use the ASN.1 Sequence for DSA/ECDSA signatures,
# so we have to convert it here for the verification to work # so we have to convert it here for the verification to work
signature = DSASignature.from_p1363(signature).dump() signature = DSASignature.from_p1363(signature).dump()

View File

@ -790,8 +790,8 @@ def _encrypt(cipher, key, data, iv, padding):
if cipher != 'rc4' and not padding: if cipher != 'rc4' and not padding:
# AES in CBC mode can be allowed with no padding if # AES in CBC mode can be allowed with no padding if
# the data is an exact multiple of the key size # the data is an exact multiple of the block size
if not (cipher == 'aes' and padding is False and len(data) % len(key) == 0): if not (cipher == 'aes' and len(data) % 16 == 0):
raise ValueError('padding must be specified') raise ValueError('padding must be specified')
if _backend == 'winlegacy': if _backend == 'winlegacy':
@ -1014,7 +1014,7 @@ def _decrypt(cipher, key, data, iv, padding):
type_name(iv) type_name(iv)
)) ))
if cipher != 'rc4' and padding is None: if cipher not in set(['rc4', 'aes']) and not padding:
raise ValueError('padding must be specified') raise ValueError('padding must be specified')
if _backend == 'winlegacy': if _backend == 'winlegacy':

View File

@ -388,7 +388,7 @@ class TLSSocket(object):
def __init__(self, address, port, timeout=10, session=None): def __init__(self, address, port, timeout=10, session=None):
""" """
:param address: :param address:
A unicode string of the domain name or IP address to conenct to A unicode string of the domain name or IP address to connect to
:param port: :param port:
An integer of the port number to connect to An integer of the port number to connect to

View File

@ -2,5 +2,5 @@
from __future__ import unicode_literals, division, absolute_import, print_function from __future__ import unicode_literals, division, absolute_import, print_function
__version__ = '1.2.1' __version__ = '1.3.0'
__version_info__ = (1, 2, 1) __version_info__ = (1, 3, 0)

View File

@ -1,8 +1,20 @@
# oscrypto # oscrypto
This is a forked version of oscrypto. It includes all the changes in [this](https://github.com/wbond/oscrypto/pull/61) Pull Request,
which makes oscrypto work properly on Ubuntu 22.04 and similar distributions
that come with OpenSSL3.
Unfortunately the developer of oscrypto didn't merge that pull request yet,
so I had to include my forked version of oscrypto in this plugin. Click [here](https://github.com/wbond/oscrypto)
to see the original, un-forked repository.
Below is the (unmodified) contents of the original readme.md:
# oscrypto
A compilation-free, always up-to-date encryption library for Python that works A compilation-free, always up-to-date encryption library for Python that works
on Windows, OS X, Linux and BSD. Supports the following versions of Python: on Windows, OS X, Linux and BSD. Supports the following versions of Python:
2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8 and pypy. 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 and pypy.
- [Supported Operating Systems](#supported-operationg-systems) - [Supported Operating Systems](#supported-operationg-systems)
- [Features](#features) - [Features](#features)
@ -19,8 +31,6 @@ on Windows, OS X, Linux and BSD. Supports the following versions of Python:
- [CI Tasks](#ci-tasks) - [CI Tasks](#ci-tasks)
[![GitHub Actions CI](https://github.com/wbond/oscrypto/workflows/CI/badge.svg)](https://github.com/wbond/oscrypto/actions?workflow=CI) [![GitHub Actions CI](https://github.com/wbond/oscrypto/workflows/CI/badge.svg)](https://github.com/wbond/oscrypto/actions?workflow=CI)
[![Travis CI](https://api.travis-ci.org/wbond/oscrypto.svg?branch=master)](https://travis-ci.org/wbond/oscrypto)
[![AppVeyor](https://ci.appveyor.com/api/projects/status/github/wbond/oscrypto?branch=master&svg=true)](https://ci.appveyor.com/project/wbond/oscrypto)
[![CircleCI](https://circleci.com/gh/wbond/oscrypto.svg?style=shield)](https://circleci.com/gh/wbond/oscrypto) [![CircleCI](https://circleci.com/gh/wbond/oscrypto.svg?style=shield)](https://circleci.com/gh/wbond/oscrypto)
[![PyPI](https://img.shields.io/pypi/v/oscrypto.svg)](https://pypi.python.org/pypi/oscrypto) [![PyPI](https://img.shields.io/pypi/v/oscrypto.svg)](https://pypi.python.org/pypi/oscrypto)
@ -58,12 +68,15 @@ care of patching vulnerabilities. Supported operating systems include:
- macOS 10.13 with LibreSSL 2.2.7 - macOS 10.13 with LibreSSL 2.2.7
- macOS 10.14 - macOS 10.14
- macOS 10.15 - macOS 10.15
- macOS 10.15 with OpenSSL 3.0
- macOS 11 - macOS 11
- macOS 12
- Linux or BSD - Linux or BSD
- Uses one of: - Uses one of:
- [OpenSSL 0.9.8](https://www.openssl.org/docs/man0.9.8/) - [OpenSSL 0.9.8](https://www.openssl.org/docs/man0.9.8/)
- [OpenSSL 1.0.x](https://www.openssl.org/docs/man1.0.0/) - [OpenSSL 1.0.x](https://www.openssl.org/docs/man1.0.0/)
- [OpenSSL 1.1.0](https://www.openssl.org/docs/man1.1.0/) - [OpenSSL 1.1.0](https://www.openssl.org/docs/man1.1.0/)
- [OpenSSL 3.0](https://www.openssl.org/docs/man3.0/)
- [LibreSSL](http://www.libressl.org/) - [LibreSSL](http://www.libressl.org/)
- Tested on: - Tested on:
- Arch Linux with OpenSSL 1.0.2 - Arch Linux with OpenSSL 1.0.2
@ -73,6 +86,7 @@ care of patching vulnerabilities. Supported operating systems include:
- Ubuntu 15.04 with OpenSSL 1.0.1 - Ubuntu 15.04 with OpenSSL 1.0.1
- Ubuntu 16.04 with OpenSSL 1.0.2 on Raspberry Pi 3 (armhf) - Ubuntu 16.04 with OpenSSL 1.0.2 on Raspberry Pi 3 (armhf)
- Ubuntu 18.04 with OpenSSL 1.1.x (amd64, arm64, ppc64el) - Ubuntu 18.04 with OpenSSL 1.1.x (amd64, arm64, ppc64el)
- Ubuntu 22.04 with OpenSSL 3.0 (amd64)
*OS X 10.6 will not be supported due to a lack of available *OS X 10.6 will not be supported due to a lack of available
cryptographic primitives and due to lack of vendor support.* cryptographic primitives and due to lack of vendor support.*
@ -199,7 +213,10 @@ Some downsides include:
## Dependencies ## Dependencies
- [*asn1crypto*](https://github.com/wbond/asn1crypto) - [*asn1crypto*](https://github.com/wbond/asn1crypto)
- Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8 or pypy - Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10 or pypy
- OpenSSL/LibreSSL if on Linux¹
*¹ On Linux, `ctypes.util.find_library()` is used to located OpenSSL. Alpine Linux does not have an appropriate install by default for `find_library()` to work properly. Instead, `oscrypto.use_openssl()` must be called with the path to the OpenSSL shared libraries.*
## Installation ## Installation
@ -220,10 +237,8 @@ pip install oscrypto
Various combinations of platforms and versions of Python are tested via: Various combinations of platforms and versions of Python are tested via:
- [AppVeyor](https://ci.appveyor.com/project/wbond/oscrypto/history) - [macOS, Linux, Windows](https://github.com/wbond/oscrypto/actions/workflows/ci.yml) via GitHub Actions
- [CircleCI](https://circleci.com/gh/wbond/oscrypto) - [arm64](https://circleci.com/gh/wbond/oscrypto) via CircleCI
- [GitHub Actions](https://github.com/wbond/oscrypto/actions)
- [Travis CI](https://travis-ci.org/wbond/oscrypto/builds)
## Testing ## Testing
@ -256,11 +271,36 @@ python run.py tests 20
python run.py tests aes 20 python run.py tests aes 20
``` ```
#### Backend Options
To run tests using a custom build of OpenSSL, or to use OpenSSL on Windows or To run tests using a custom build of OpenSSL, or to use OpenSSL on Windows or
Mac, add `use_openssl` after `run.py`, like: Mac, add `use_openssl` after `run.py`, like:
```bash ```bash
python run.py use_openssl=/path/to/libcrypto.dylib,/path/to/libssl.dylib tests python run.py use_openssl=/path/to/libcrypto.so,/path/to/libssl.so tests
```
To run tests forcing the use of ctypes, even if cffi is installed, add
`use_ctypes` after `run.py`:
```bash
python run.py use_ctypes=true tests
```
To run tests using the legacy Windows crypto functions on Windows 7+, add
`use_winlegacy` after `run.py`:
```bash
python run.py use_winlegacy=true tests
```
#### Internet Tests
To skip tests that require an internet connection, add `skip_internet` after
`run.py`:
```bash
python run.py skip_internet=true tests
``` ```
### PyPi Source Distribution ### PyPi Source Distribution
@ -272,6 +312,45 @@ PyPi, the full test suite is run via:
python setup.py test python setup.py test
``` ```
#### Test Options
The following env vars can control aspects of running tests:
##### Force OpenSSL Shared Library Paths
Setting the env var `OSCRYPTO_USE_OPENSSL` to a string in the form:
```
/path/to/libcrypto.so,/path/to/libssl.so
```
will force use of specific OpenSSL shared libraries.
This also works on Mac and Windows to force use of OpenSSL instead of using
native crypto libraries.
##### Force Use of ctypes
By default, oscrypto will use the `cffi` module for FFI if it is installed.
To use the slightly slower, but more widely-tested, `ctypes` FFI layer, set
the env var `OPENSSL_USE_CTYPES=true`.
##### Force Use of Legacy Windows Crypto APIs
On Windows 7 and newer, oscrypto will use the CNG backend by default.
To force use of the older CryptoAPI, set the env var
`OPENSSL_USE_WINLEGACY=true`.
##### Skip Tests Requiring an Internet Connection
Some of the TLS tests require an active internet connection to ensure that
various "bad" server certificates are rejected.
To skip tests requiring an internet connection, set the env var
`OPENSSL_SKIP_INTERNET_TESTS=true`.
### Package ### Package
When the package has been installed via pip (or another method), the package When the package has been installed via pip (or another method), the package
@ -345,7 +424,7 @@ PowerShell with `Net.WebClient` is used. This configuration sidesteps issues
related to getting pip to work properly and messing with `site-packages` for related to getting pip to work properly and messing with `site-packages` for
the version of Python being used. the version of Python being used.
The `ci` task runs `lint` (if flake8 is avaiable for the version of Python) and The `ci` task runs `lint` (if flake8 is available for the version of Python) and
`coverage` (or `tests` if coverage is not available for the version of Python). `coverage` (or `tests` if coverage is not available for the version of Python).
If the current directory is a clean git working copy, the coverage data is If the current directory is a clean git working copy, the coverage data is
submitted to codecov.io. submitted to codecov.io.

View File

@ -10,7 +10,7 @@ from setuptools.command.egg_info import egg_info
PACKAGE_NAME = 'oscrypto' PACKAGE_NAME = 'oscrypto'
PACKAGE_VERSION = '1.2.1' PACKAGE_VERSION = '1.3.0'
PACKAGE_ROOT = os.path.dirname(os.path.abspath(__file__)) PACKAGE_ROOT = os.path.dirname(os.path.abspath(__file__))
@ -126,8 +126,10 @@ setup(
'License :: OSI Approved :: MIT License', 'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.4',
@ -135,6 +137,8 @@ setup(
'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: Implementation :: PyPy', 'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Security :: Cryptography', 'Topic :: Security :: Cryptography',
@ -142,7 +146,7 @@ setup(
keywords='crypto pki tls ssl x509 certificate encrypt decrypt sign verify rsa dsa ec dh', keywords='crypto pki tls ssl x509 certificate encrypt decrypt sign verify rsa dsa ec dh',
install_requires=['asn1crypto>=1.0.0'], install_requires=['asn1crypto>=1.5.1'],
packages=find_packages(exclude=['tests*', 'dev*']), packages=find_packages(exclude=['tests*', 'dev*']),
package_data=package_data, package_data=package_data,

View File

@ -20,6 +20,15 @@
"singleinstance": false "singleinstance": false
}, },
"standalone_recently_opened": [ "standalone_recently_opened": [
{
"authors": [
"Asato Asato"
],
"key": "/home/marc/Calibre-Bibliothek/Asato Asato/86--EIGHTY-SIX, Vol. 1 (light novel) (86--EIGHTY-SIX (light novel)) (940)/86--EIGHTY-SIX, Vol. 1 (light novel) (86-- - Asato Asato.epub",
"pathtoebook": "/home/marc/Calibre-Bibliothek/Asato Asato/86--EIGHTY-SIX, Vol. 1 (light novel) (86--EIGHTY-SIX (light novel)) (940)/86--EIGHTY-SIX, Vol. 1 (light novel) (86-- - Asato Asato.epub",
"timestamp": "2022-09-15T19:09:07.062Z",
"title": "86--EIGHTY-SIX, Vol. 1 (light novel) (86--EIGHTY-SIX (light novel))"
},
{ {
"authors": [ "authors": [
"Magica Quartet, Hanokage" "Magica Quartet, Hanokage"

View File

@ -0,0 +1 @@
[{"pos": "epubcfi(/2/2/4/2/2/1:5)", "pos_type": "epubcfi", "timestamp": "2022-08-13T14:14:47.055838+00:00", "type": "last-read"}]

View File

@ -0,0 +1 @@
[{"pos": "epubcfi(/2/2/4/2@50:50)", "pos_type": "epubcfi", "timestamp": "2022-09-15T19:10:13.146451+00:00", "type": "last-read"}]