Keep aspect ratio for epub output and other changes
This commit is contained in:
parent
6838251d3b
commit
e74a0e6ff3
@ -6,7 +6,7 @@ json:{
|
||||
"epub_inline_toc": false,
|
||||
"epub_toc_at_end": false,
|
||||
"toc_title": null,
|
||||
"preserve_cover_aspect_ratio": false,
|
||||
"preserve_cover_aspect_ratio": true,
|
||||
"epub_flatten": false,
|
||||
"epub_version": "3"
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
"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 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-last-used-filter-spec-all-files": false,
|
||||
"database location dialog": "/home/marc/Nextcloud/Books",
|
||||
@ -18,7 +18,7 @@
|
||||
]
|
||||
},
|
||||
"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": [
|
||||
[
|
||||
"title",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
{
|
||||
"Plugin Updater plugin:plugin updater dialog": {
|
||||
"__class__": "bytearray",
|
||||
"__value__": "AdnQywADAAAAAAHDAAABFQAABdUAAAMKAAABxQAAARcAAAXTAAADCAAAAAAAAAAAB4AAAAHFAAABFwAABdMAAAMI"
|
||||
"__value__": "AdnQywADAAAAAAHHAAABFQAABdkAAAMKAAAByQAAARcAAAXXAAADCAAAAAAAAAAAB4AAAAHJAAABFwAABdcAAAMI"
|
||||
},
|
||||
"action-layout-toolbar": [
|
||||
"Add Books",
|
||||
@ -80,13 +80,21 @@
|
||||
"__value__": "AdnQywADAAAAAAAbAAAAEgAAB1gAAAQbAAAAHQAAABQAAAdWAAAEGQAAAAAAAAAAB4AAAAAdAAAAFAAAB1YAAAQZ"
|
||||
},
|
||||
"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": {
|
||||
"__class__": "bytearray",
|
||||
"__value__": "AdnQywADAAAAAAASAAAAJAAAB08AAAP7AAAAFAAAACYAAAdNAAAD+QAAAAAAAAAAB4AAAAAUAAAAJgAAB00AAAP5"
|
||||
"__value__": "AdnQywADAAAAAAAeAAAAJAAAB1sAAAP7AAAAIAAAACYAAAdZAAAD+QAAAAAAAAAAB4AAAAAgAAAAJgAAB1kAAAP5"
|
||||
},
|
||||
"convert_single_dialog_geom": {
|
||||
"__class__": "bytearray",
|
||||
"__value__": "AdnQywADAAAAAAAsAAAAEgAAB2kAAAPpAAAALgAAABQAAAdnAAAD5wAAAAAAAAAAB4AAAAAuAAAAFAAAB2cAAAPn"
|
||||
"__value__": "AdnQywADAAAAAAA4AAAAEgAAB3UAAAPpAAAAOgAAABQAAAdzAAAD5wAAAAAAAAAAB4AAAAA6AAAAFAAAB3MAAAPn"
|
||||
},
|
||||
"cover_browser_splitter_vertical_state": [
|
||||
false,
|
||||
@ -196,7 +204,7 @@
|
||||
},
|
||||
"duplicates-question-dialog-geometry": {
|
||||
"__class__": "bytearray",
|
||||
"__value__": "AdnQywADAAAAAACGAAABTAAAA2AAAALPAAAAiAAAAU4AAANeAAACzQAAAAAAAAAAB4AAAACIAAABTgAAA14AAALN"
|
||||
"__value__": "AdnQywADAAAAAACOAAABTAAAA2gAAALPAAAAkAAAAU4AAANmAAACzQAAAAAAAAAAB4AAAACQAAABTgAAA2YAAALN"
|
||||
},
|
||||
"grid view visible": false,
|
||||
"jobs view column layout3": {
|
||||
@ -208,7 +216,7 @@
|
||||
"__value__": "AdnQywADAAAAAAGYAAABEQAABQ4AAAMyAAABmgAAARMAAAUMAAADMAAAAAAAAAAAB4AAAAGaAAABEwAABQwAAAMw"
|
||||
},
|
||||
"library_usage_stats": {
|
||||
"/home/marc/Calibre-Bibliothek": 184
|
||||
"/home/marc/Calibre-Bibliothek": 200
|
||||
},
|
||||
"metadata-download-identify-widget-splitter-state": {
|
||||
"__class__": "bytearray",
|
||||
@ -220,15 +228,15 @@
|
||||
},
|
||||
"metasingle_window_geometry3": {
|
||||
"__class__": "bytearray",
|
||||
"__value__": "AdnQywADAAAAAAA8AAAAEgAAB3kAAAQbAAAAPgAAABQAAAd3AAAEGQAAAAAAAAAAB4AAAAA+AAAAFAAAB3cAAAQZ"
|
||||
"__value__": "AdnQywADAAAAAAA+AAAAEgAAB3sAAAQbAAAAQAAAABQAAAd5AAAEGQAAAAAAAAAAB4AAAABAAAAAFAAAB3kAAAQZ"
|
||||
},
|
||||
"plugin config dialog:Dateityp:DeACSM": {
|
||||
"__class__": "bytearray",
|
||||
"__value__": "AdnQywADAAAAAAMiAAABRAAABKAAAAL9AAADJAAAAUYAAASeAAAC+wAAAAAAAAAAB4AAAAMkAAABRgAABJ4AAAL7"
|
||||
"__value__": "AdnQywADAAAAAAMkAAABRAAABKIAAAL9AAADJgAAAUYAAASgAAAC+wAAAAAAAAAAB4AAAAMmAAABRgAABKAAAAL7"
|
||||
},
|
||||
"plugin config dialog:Dateityp:DeDRM": {
|
||||
"__class__": "bytearray",
|
||||
"__value__": "AdnQywADAAAAAAP8AAAApAAABRYAAAKaAAAD/gAAAKYAAAUUAAACmAAAAAAAAAAAB4AAAAP+AAAApgAABRQAAAKY"
|
||||
"__value__": "AdnQywADAAAAAAQAAAAApAAABRoAAAKaAAAEAgAAAKYAAAUYAAACmAAAAAAAAAAAB4AAAAQCAAAApgAABRgAAAKY"
|
||||
},
|
||||
"preferences dialog geometry": {
|
||||
"__class__": "bytearray",
|
||||
|
Binary file not shown.
@ -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
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
@ -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
|
@ -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.
|
@ -30,6 +30,7 @@ from .algos import (
|
||||
_ForceNullParameters,
|
||||
DigestAlgorithm,
|
||||
EncryptionAlgorithm,
|
||||
EncryptionAlgorithmId,
|
||||
HmacAlgorithm,
|
||||
KdfAlgorithm,
|
||||
RSAESOAEPParams,
|
||||
@ -100,6 +101,8 @@ class CMSAttributeType(ObjectIdentifier):
|
||||
'1.2.840.113549.1.9.4': 'message_digest',
|
||||
'1.2.840.113549.1.9.5': 'signing_time',
|
||||
'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
|
||||
'1.2.840.113549.1.9.16.2.11': 'encrypt_key_pref',
|
||||
# https://tools.ietf.org/html/rfc3161#page-20
|
||||
@ -273,7 +276,7 @@ class V2Form(Sequence):
|
||||
class AttCertIssuer(Choice):
|
||||
_alternatives = [
|
||||
('v1_form', GeneralNames),
|
||||
('v2_form', V2Form, {'explicit': 0}),
|
||||
('v2_form', V2Form, {'implicit': 0}),
|
||||
]
|
||||
|
||||
|
||||
@ -315,7 +318,7 @@ class SetOfSvceAuthInfo(SetOf):
|
||||
class RoleSyntax(Sequence):
|
||||
_fields = [
|
||||
('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):
|
||||
_fields = [
|
||||
('type', ObjectIdentifier, {'implicit': 0}),
|
||||
('value', Any, {'implicit': 1}),
|
||||
('value', Any, {'explicit': 1}),
|
||||
]
|
||||
|
||||
|
||||
@ -347,9 +350,9 @@ class SetOfSecurityCategory(SetOf):
|
||||
|
||||
class Clearance(Sequence):
|
||||
_fields = [
|
||||
('policy_id', ObjectIdentifier, {'implicit': 0}),
|
||||
('class_list', ClassList, {'implicit': 1, 'default': 'unclassified'}),
|
||||
('security_categories', SetOfSecurityCategory, {'implicit': 2, 'optional': True}),
|
||||
('policy_id', ObjectIdentifier),
|
||||
('class_list', ClassList, {'default': set(['unclassified'])}),
|
||||
('security_categories', SetOfSecurityCategory, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
@ -946,6 +949,21 @@ class SMIMEEncryptionKeyPreferences(SetOf):
|
||||
_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 = {
|
||||
'data': OctetString,
|
||||
'signed_data': SignedData,
|
||||
@ -981,4 +999,5 @@ CMSAttribute._oid_specs = {
|
||||
'microsoft_nested_signature': SetOfContentInfo,
|
||||
'microsoft_time_stamp_token': SetOfContentInfo,
|
||||
'encrypt_key_pref': SMIMEEncryptionKeyPreferences,
|
||||
'smime_capabilities': SetOfSMIMECapabilites,
|
||||
}
|
||||
|
@ -4113,6 +4113,10 @@ class Sequence(Asn1Value):
|
||||
if self._header is not None and self._header[-1:] == b'\x80':
|
||||
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:
|
||||
self._set_contents(force=force)
|
||||
|
||||
|
@ -752,7 +752,7 @@ class PrivateKeyInfo(Sequence):
|
||||
type_name(private_key)
|
||||
))
|
||||
|
||||
if algorithm == 'rsa':
|
||||
if algorithm == 'rsa' or algorithm == 'rsassa_pss':
|
||||
if not isinstance(private_key, RSAPrivateKey):
|
||||
private_key = RSAPrivateKey.load(private_key)
|
||||
params = Null()
|
||||
@ -893,7 +893,7 @@ class PrivateKeyInfo(Sequence):
|
||||
def algorithm(self):
|
||||
"""
|
||||
:return:
|
||||
A unicode string of "rsa", "dsa" or "ec"
|
||||
A unicode string of "rsa", "rsassa_pss", "dsa" or "ec"
|
||||
"""
|
||||
|
||||
if self._algorithm is None:
|
||||
@ -908,7 +908,7 @@ class PrivateKeyInfo(Sequence):
|
||||
"""
|
||||
|
||||
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
|
||||
elif self.algorithm == 'dsa':
|
||||
prime = self['private_key_algorithm']['parameters']['p'].native
|
||||
@ -1120,7 +1120,7 @@ class PublicKeyInfo(Sequence):
|
||||
type_name(public_key)
|
||||
))
|
||||
|
||||
if algorithm != 'rsa':
|
||||
if algorithm != 'rsa' and algorithm != 'rsassa_pss':
|
||||
raise ValueError(unwrap(
|
||||
'''
|
||||
algorithm must "rsa", not %s
|
||||
@ -1222,7 +1222,7 @@ class PublicKeyInfo(Sequence):
|
||||
def algorithm(self):
|
||||
"""
|
||||
:return:
|
||||
A unicode string of "rsa", "dsa" or "ec"
|
||||
A unicode string of "rsa", "rsassa_pss", "dsa" or "ec"
|
||||
"""
|
||||
|
||||
if self._algorithm is None:
|
||||
@ -1240,7 +1240,7 @@ class PublicKeyInfo(Sequence):
|
||||
if self.algorithm == 'ec':
|
||||
self._bit_size = int(((len(self['public_key'].native) - 1) / 2) * 8)
|
||||
else:
|
||||
if self.algorithm == 'rsa':
|
||||
if self.algorithm == 'rsa' or self.algorithm == 'rsassa_pss':
|
||||
prime = self['public_key'].parsed['modulus'].native
|
||||
elif self.algorithm == 'dsa':
|
||||
prime = self['algorithm']['parameters']['p'].native
|
||||
|
@ -2,5 +2,5 @@
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
|
||||
__version__ = '1.4.0'
|
||||
__version_info__ = (1, 4, 0)
|
||||
__version__ = '1.5.1'
|
||||
__version_info__ = (1, 5, 1)
|
||||
|
@ -1,5 +1,43 @@
|
||||
# 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
|
||||
|
||||
- `core.ObjectIdentifier` and all derived classes now obey X.660 §7.6 and
|
||||
|
@ -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)
|
||||
```
|
@ -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`
|
@ -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)
|
||||
```
|
@ -9,6 +9,7 @@ A fast, pure Python library for parsing and serializing ASN.1 structures.
|
||||
- [Dependencies](#dependencies)
|
||||
- [Installation](#installation)
|
||||
- [License](#license)
|
||||
- [Security Policy](#security-policy)
|
||||
- [Documentation](#documentation)
|
||||
- [Continuous Integration](#continuous-integration)
|
||||
- [Testing](#testing)
|
||||
@ -109,11 +110,11 @@ faster to an order of magnitude or more.
|
||||
|
||||
## Current Release
|
||||
|
||||
1.4.0 - [changelog](changelog.md)
|
||||
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 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.*
|
||||
|
||||
## Installation
|
||||
@ -127,6 +128,11 @@ pip install asn1crypto
|
||||
*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
|
||||
|
@ -0,0 +1,4 @@
|
||||
[egg_info]
|
||||
tag_build =
|
||||
tag_date = 0
|
||||
|
@ -10,7 +10,7 @@ from setuptools.command.egg_info import egg_info
|
||||
|
||||
|
||||
PACKAGE_NAME = 'asn1crypto'
|
||||
PACKAGE_VERSION = '1.4.0'
|
||||
PACKAGE_VERSION = '1.5.1'
|
||||
PACKAGE_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
@ -138,6 +138,7 @@ setup(
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
@ -1 +1 @@
|
||||
2021-12-15-01
|
||||
2022-07-31-01
|
@ -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
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
@ -1,6 +1,6 @@
|
||||
Metadata-Version: 2.1
|
||||
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.
|
||||
Home-page: https://github.com/wbond/oscrypto
|
||||
Author: wbond
|
||||
@ -10,7 +10,7 @@ Description: # oscrypto
|
||||
|
||||
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:
|
||||
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)
|
||||
- [Features](#features)
|
||||
@ -27,8 +27,6 @@ Description: # oscrypto
|
||||
- [CI Tasks](#ci-tasks)
|
||||
|
||||
[![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)
|
||||
[![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.14
|
||||
- macOS 10.15
|
||||
- macOS 10.15 with OpenSSL 3.0
|
||||
- macOS 11
|
||||
- macOS 12
|
||||
- Linux or BSD
|
||||
- Uses one of:
|
||||
- [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.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/)
|
||||
- Tested on:
|
||||
- Arch Linux with OpenSSL 1.0.2
|
||||
@ -81,6 +82,7 @@ Description: # oscrypto
|
||||
- Ubuntu 15.04 with OpenSSL 1.0.1
|
||||
- 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 22.04 with OpenSSL 3.0 (amd64)
|
||||
|
||||
*OS X 10.6 will not be supported due to a lack of available
|
||||
cryptographic primitives and due to lack of vendor support.*
|
||||
@ -207,7 +209,10 @@ Description: # oscrypto
|
||||
## Dependencies
|
||||
|
||||
- [*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
|
||||
|
||||
@ -228,10 +233,8 @@ Description: # oscrypto
|
||||
|
||||
Various combinations of platforms and versions of Python are tested via:
|
||||
|
||||
- [AppVeyor](https://ci.appveyor.com/project/wbond/oscrypto/history)
|
||||
- [CircleCI](https://circleci.com/gh/wbond/oscrypto)
|
||||
- [GitHub Actions](https://github.com/wbond/oscrypto/actions)
|
||||
- [Travis CI](https://travis-ci.org/wbond/oscrypto/builds)
|
||||
- [macOS, Linux, Windows](https://github.com/wbond/oscrypto/actions/workflows/ci.yml) via GitHub Actions
|
||||
- [arm64](https://circleci.com/gh/wbond/oscrypto) via CircleCI
|
||||
|
||||
## Testing
|
||||
|
||||
@ -264,11 +267,36 @@ Description: # oscrypto
|
||||
python run.py tests aes 20
|
||||
```
|
||||
|
||||
#### Backend Options
|
||||
|
||||
To run tests using a custom build of OpenSSL, or to use OpenSSL on Windows or
|
||||
Mac, add `use_openssl` after `run.py`, like:
|
||||
|
||||
```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
|
||||
@ -280,6 +308,45 @@ Description: # oscrypto
|
||||
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
|
||||
|
||||
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
|
||||
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).
|
||||
If the current directory is a clean git working copy, the coverage data is
|
||||
submitted to codecov.io.
|
||||
@ -368,8 +435,10 @@ Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
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
|
||||
@ -377,6 +446,8 @@ 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 :: PyPy
|
||||
Classifier: Topic :: Security :: Cryptography
|
||||
Description-Content-Type: text/markdown
|
||||
|
@ -1,5 +1,22 @@
|
||||
# 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
|
||||
|
||||
- Fix running in an environment with a custom OpenSSL install on macOS 10.15
|
||||
|
@ -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
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
@ -1,6 +1,6 @@
|
||||
Metadata-Version: 2.1
|
||||
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.
|
||||
Home-page: https://github.com/wbond/oscrypto
|
||||
Author: wbond
|
||||
@ -10,7 +10,7 @@ Description: # oscrypto
|
||||
|
||||
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:
|
||||
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)
|
||||
- [Features](#features)
|
||||
@ -27,8 +27,6 @@ Description: # oscrypto
|
||||
- [CI Tasks](#ci-tasks)
|
||||
|
||||
[![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)
|
||||
[![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.14
|
||||
- macOS 10.15
|
||||
- macOS 10.15 with OpenSSL 3.0
|
||||
- macOS 11
|
||||
- macOS 12
|
||||
- Linux or BSD
|
||||
- Uses one of:
|
||||
- [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.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/)
|
||||
- Tested on:
|
||||
- Arch Linux with OpenSSL 1.0.2
|
||||
@ -81,6 +82,7 @@ Description: # oscrypto
|
||||
- Ubuntu 15.04 with OpenSSL 1.0.1
|
||||
- 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 22.04 with OpenSSL 3.0 (amd64)
|
||||
|
||||
*OS X 10.6 will not be supported due to a lack of available
|
||||
cryptographic primitives and due to lack of vendor support.*
|
||||
@ -207,7 +209,10 @@ Description: # oscrypto
|
||||
## Dependencies
|
||||
|
||||
- [*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
|
||||
|
||||
@ -228,10 +233,8 @@ Description: # oscrypto
|
||||
|
||||
Various combinations of platforms and versions of Python are tested via:
|
||||
|
||||
- [AppVeyor](https://ci.appveyor.com/project/wbond/oscrypto/history)
|
||||
- [CircleCI](https://circleci.com/gh/wbond/oscrypto)
|
||||
- [GitHub Actions](https://github.com/wbond/oscrypto/actions)
|
||||
- [Travis CI](https://travis-ci.org/wbond/oscrypto/builds)
|
||||
- [macOS, Linux, Windows](https://github.com/wbond/oscrypto/actions/workflows/ci.yml) via GitHub Actions
|
||||
- [arm64](https://circleci.com/gh/wbond/oscrypto) via CircleCI
|
||||
|
||||
## Testing
|
||||
|
||||
@ -264,11 +267,36 @@ Description: # oscrypto
|
||||
python run.py tests aes 20
|
||||
```
|
||||
|
||||
#### Backend Options
|
||||
|
||||
To run tests using a custom build of OpenSSL, or to use OpenSSL on Windows or
|
||||
Mac, add `use_openssl` after `run.py`, like:
|
||||
|
||||
```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
|
||||
@ -280,6 +308,45 @@ Description: # oscrypto
|
||||
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
|
||||
|
||||
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
|
||||
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).
|
||||
If the current directory is a clean git working copy, the coverage data is
|
||||
submitted to codecov.io.
|
||||
@ -368,8 +435,10 @@ Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
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
|
||||
@ -377,6 +446,8 @@ 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 :: PyPy
|
||||
Classifier: Topic :: Security :: Cryptography
|
||||
Description-Content-Type: text/markdown
|
||||
|
@ -1,3 +1,4 @@
|
||||
LICENSE
|
||||
setup.py
|
||||
oscrypto/__init__.py
|
||||
oscrypto/_asn1.py
|
||||
|
@ -1 +1 @@
|
||||
asn1crypto>=1.0.0
|
||||
asn1crypto>=1.5.1
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -241,10 +241,12 @@ def _unwrap_private_key_info(key_info):
|
||||
- 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
|
||||
|
||||
if key_info.algorithm == 'dsa':
|
||||
if key_alg == 'dsa':
|
||||
params = key_info['private_key_algorithm']['parameters']
|
||||
parsed = key_info['private_key'].parsed
|
||||
return DSAPrivateKey({
|
||||
@ -260,7 +262,7 @@ def _unwrap_private_key_info(key_info):
|
||||
'private_key': parsed,
|
||||
})
|
||||
|
||||
if key_info.algorithm == 'ec':
|
||||
if key_alg == 'ec':
|
||||
parsed = key_info['private_key'].parsed
|
||||
parsed['parameters'] = key_info['private_key_algorithm']['parameters']
|
||||
return parsed
|
||||
@ -660,7 +662,7 @@ def _unarmor_pem(data, password=None):
|
||||
data = data.strip()
|
||||
|
||||
# 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']):
|
||||
algo = armor_type.group(2).lower()
|
||||
return ('private key', algo, _unarmor_pem_openssl_private(headers, der_bytes, password))
|
||||
|
@ -119,6 +119,7 @@ class SecurityConst():
|
||||
CSSMERR_TP_CERT_NOT_VALID_YET = -2147409653
|
||||
CSSMERR_TP_CERT_REVOKED = -2147409652
|
||||
CSSMERR_TP_NOT_TRUSTED = -2147409622
|
||||
CSSMERR_TP_CERT_SUSPENDED = -2147409651
|
||||
|
||||
CSSM_CERT_X_509v3 = 0x00000004
|
||||
|
||||
|
@ -250,8 +250,18 @@ class Certificate(_CertificateBase):
|
||||
"""
|
||||
|
||||
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 *')
|
||||
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)
|
||||
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'])
|
||||
@ -274,6 +284,8 @@ class Certificate(_CertificateBase):
|
||||
|
||||
if signature_algo == 'rsassa_pkcs1v15':
|
||||
verify_func = rsa_pkcs1v15_verify
|
||||
elif signature_algo == 'rsassa_pss':
|
||||
verify_func = rsa_pss_verify
|
||||
elif signature_algo == 'dsa':
|
||||
verify_func = dsa_verify
|
||||
elif signature_algo == 'ecdsa':
|
||||
@ -832,6 +844,13 @@ def _load_key(key_object):
|
||||
))
|
||||
|
||||
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()
|
||||
item_type = SecurityConst.kSecItemTypePublicKey
|
||||
|
||||
@ -1392,7 +1411,8 @@ def rsa_pss_verify(certificate_or_public_key, signature, data, hash_algorithm):
|
||||
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')
|
||||
|
||||
hash_length = {
|
||||
@ -1735,7 +1755,8 @@ def rsa_pss_sign(private_key, data, hash_algorithm):
|
||||
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')
|
||||
|
||||
hash_length = {
|
||||
|
@ -50,6 +50,7 @@ from .._tls import (
|
||||
raise_expired_not_yet_valid,
|
||||
raise_handshake,
|
||||
raise_hostname,
|
||||
raise_lifetime_too_long,
|
||||
raise_no_issuer,
|
||||
raise_protocol_error,
|
||||
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
|
||||
|
||||
:param connection_id:
|
||||
An integer identifing the connection
|
||||
An integer identifying the connection
|
||||
|
||||
:param data_buffer:
|
||||
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
|
||||
|
||||
:param connection_id:
|
||||
An integer identifing the connection
|
||||
An integer identifying the connection
|
||||
|
||||
:param data_buffer:
|
||||
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):
|
||||
"""
|
||||
: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:
|
||||
An integer of the port number to connect to
|
||||
@ -875,6 +876,7 @@ class TLSSocket(object):
|
||||
expired = result_code == SecurityConst.CSSMERR_TP_CERT_EXPIRED
|
||||
not_yet_valid = result_code == SecurityConst.CSSMERR_TP_CERT_NOT_VALID_YET
|
||||
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
|
||||
if osx_version_info >= (10, 12):
|
||||
@ -903,6 +905,9 @@ class TLSSocket(object):
|
||||
elif self_signed:
|
||||
raise_self_signed(cert)
|
||||
|
||||
elif validity_too_long:
|
||||
raise_lifetime_too_long(cert)
|
||||
|
||||
if detect_client_auth_request(self._server_hello):
|
||||
raise_client_auth()
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -5,6 +5,15 @@ from .. import ffi
|
||||
from .._ffi import buffer_from_bytes, byte_string_from_buffer, null
|
||||
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':
|
||||
from ._libcrypto_cffi import (
|
||||
libcrypto,
|
||||
@ -22,6 +31,7 @@ else:
|
||||
__all__ = [
|
||||
'handle_openssl_error',
|
||||
'libcrypto',
|
||||
'libcrypto_legacy_support',
|
||||
'libcrypto_version',
|
||||
'libcrypto_version_info',
|
||||
'LibcryptoConst',
|
||||
@ -38,6 +48,18 @@ if libcrypto_version_info < (1, 1):
|
||||
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):
|
||||
|
||||
try:
|
||||
@ -57,7 +79,7 @@ def _try_decode(value):
|
||||
|
||||
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
|
||||
|
||||
:param result:
|
||||
@ -95,9 +117,15 @@ def peek_openssl_error():
|
||||
"""
|
||||
|
||||
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)
|
||||
|
||||
|
@ -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
|
@ -74,6 +74,16 @@ if version_info < (1, 1):
|
||||
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,
|
||||
# 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.
|
||||
@ -140,7 +150,6 @@ ffi.cdef("""
|
||||
EVP_PKEY *X509_get_pubkey(X509 *x);
|
||||
void X509_free(X509 *a);
|
||||
|
||||
int EVP_PKEY_size(EVP_PKEY *pkey);
|
||||
RSA *EVP_PKEY_get1_RSA(EVP_PKEY *pkey);
|
||||
void RSA_free(RSA *r);
|
||||
|
||||
@ -196,6 +205,15 @@ ffi.cdef("""
|
||||
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):
|
||||
ffi.cdef("""
|
||||
EVP_MD_CTX *EVP_MD_CTX_create(void);
|
||||
|
@ -73,6 +73,8 @@ P_EVP_MD_CTX = c_void_p
|
||||
P_EVP_MD = 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
|
||||
EVP_PKEY_CTX = c_void_p
|
||||
@ -97,6 +99,13 @@ try:
|
||||
libcrypto.ERR_free_strings.argtypes = []
|
||||
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.restype = c_ulong
|
||||
|
||||
@ -301,10 +310,16 @@ try:
|
||||
libcrypto.EVP_sha512.argtypes = []
|
||||
libcrypto.EVP_sha512.restype = P_EVP_MD
|
||||
|
||||
if version_info < (3, 0):
|
||||
libcrypto.EVP_PKEY_size.argtypes = [
|
||||
P_EVP_PKEY
|
||||
]
|
||||
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 = [
|
||||
P_EVP_PKEY
|
||||
|
@ -32,6 +32,7 @@ from .._ffi import (
|
||||
new,
|
||||
null,
|
||||
unwrap,
|
||||
write_to_buffer,
|
||||
)
|
||||
from ._libcrypto import libcrypto, LibcryptoConst, libcrypto_version_info, handle_openssl_error
|
||||
from ..errors import AsymmetricKeyError, IncompleteAsymmetricKeyError, SignatureError
|
||||
@ -105,6 +106,16 @@ class PrivateKey(_PrivateKeyBase):
|
||||
pubkey_data = bytes_from_buffer(pubkey_buffer, pubkey_length)
|
||||
|
||||
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)
|
||||
if is_null(pub_evp_pkey):
|
||||
handle_openssl_error(0)
|
||||
@ -212,8 +223,13 @@ class Certificate(_CertificateBase):
|
||||
"""
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
@ -233,6 +249,8 @@ class Certificate(_CertificateBase):
|
||||
|
||||
if signature_algo == 'rsassa_pkcs1v15':
|
||||
verify_func = rsa_pkcs1v15_verify
|
||||
elif signature_algo == 'rsassa_pss':
|
||||
verify_func = rsa_pss_verify
|
||||
elif signature_algo == 'dsa':
|
||||
verify_func = dsa_verify
|
||||
elif signature_algo == 'ecdsa':
|
||||
@ -692,7 +710,7 @@ def load_public_key(source):
|
||||
source must be a byte string, unicode string or
|
||||
asn1crypto.keys.PublicKeyInfo object, not %s
|
||||
''',
|
||||
type_name(public_key)
|
||||
type_name(source)
|
||||
))
|
||||
|
||||
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()
|
||||
|
||||
buffer = buffer_from_bytes(data)
|
||||
evp_pkey = libcrypto.d2i_PUBKEY(null(), buffer_pointer(buffer), len(data))
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
Encrypts plaintext using an RSA public key or certificate
|
||||
@ -970,7 +1012,7 @@ def _encrypt(certificate_or_public_key, data, padding):
|
||||
rsa = None
|
||||
|
||||
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)
|
||||
|
||||
rsa = libcrypto.EVP_PKEY_get1_RSA(certificate_or_public_key.evp_pkey)
|
||||
@ -1025,7 +1067,7 @@ def _decrypt(private_key, ciphertext, padding):
|
||||
rsa = None
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
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(
|
||||
'''
|
||||
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)
|
||||
))
|
||||
|
||||
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'])
|
||||
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'])
|
||||
|
||||
if hash_algorithm not in valid_hash_algorithms:
|
||||
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"'
|
||||
raise ValueError(pretty_message(
|
||||
'''
|
||||
@ -1251,16 +1298,16 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
|
||||
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(
|
||||
'''
|
||||
PSS padding can only be used with RSA keys - the key provided is a
|
||||
%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:
|
||||
raise ValueError(pretty_message(
|
||||
'''
|
||||
@ -1279,7 +1326,7 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
|
||||
if is_null(rsa):
|
||||
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_length = libcrypto.RSA_public_decrypt(
|
||||
len(signature),
|
||||
@ -1323,14 +1370,14 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
|
||||
}[hash_algorithm]()
|
||||
|
||||
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()
|
||||
|
||||
rsa = libcrypto.EVP_PKEY_get1_RSA(certificate_or_public_key.evp_pkey)
|
||||
if is_null(rsa):
|
||||
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_length = libcrypto.RSA_public_decrypt(
|
||||
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
|
||||
)
|
||||
|
||||
elif certificate_or_public_key.algorithm == 'rsa':
|
||||
elif cp_is_rsa:
|
||||
res = libcrypto.EVP_DigestInit_ex(evp_md_ctx, evp_md, null())
|
||||
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
|
||||
)
|
||||
|
||||
elif certificate_or_public_key.algorithm == 'dsa':
|
||||
elif cp_alg == 'dsa':
|
||||
digest = getattr(hashlib, hash_algorithm)(data).digest()
|
||||
|
||||
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)
|
||||
|
||||
elif certificate_or_public_key.algorithm == 'ec':
|
||||
elif cp_alg == 'ec':
|
||||
digest = getattr(hashlib, hash_algorithm)(data).digest()
|
||||
|
||||
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)
|
||||
|
||||
# Use the hash algorithm output length as the salt length
|
||||
if libcrypto_version_info < (3, 0):
|
||||
res = libcrypto.EVP_PKEY_CTX_ctrl(
|
||||
evp_pkey_ctx_pointer,
|
||||
LibcryptoConst.EVP_PKEY_RSA,
|
||||
@ -1519,12 +1567,14 @@ def rsa_pss_sign(private_key, data, hash_algorithm):
|
||||
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(
|
||||
'''
|
||||
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)
|
||||
@ -1637,13 +1687,16 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
|
||||
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'])
|
||||
if private_key.algorithm == 'rsa' and not rsa_pss_padding:
|
||||
if pkey_alg == 'rsa' and not rsa_pss_padding:
|
||||
valid_hash_algorithms |= set(['raw'])
|
||||
|
||||
if hash_algorithm not in valid_hash_algorithms:
|
||||
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"'
|
||||
raise ValueError(pretty_message(
|
||||
'''
|
||||
@ -1653,16 +1706,16 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
|
||||
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(
|
||||
'''
|
||||
PSS padding can only be used with RSA keys - the key provided is a
|
||||
%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:
|
||||
raise ValueError(pretty_message(
|
||||
'''
|
||||
@ -1681,7 +1734,7 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
|
||||
if is_null(rsa):
|
||||
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_length = libcrypto.RSA_private_encrypt(
|
||||
@ -1722,14 +1775,14 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
|
||||
}[hash_algorithm]()
|
||||
|
||||
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()
|
||||
|
||||
rsa = libcrypto.EVP_PKEY_get1_RSA(private_key.evp_pkey)
|
||||
if is_null(rsa):
|
||||
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)
|
||||
res = libcrypto.RSA_padding_add_PKCS1_PSS(
|
||||
rsa,
|
||||
@ -1750,8 +1803,8 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
|
||||
)
|
||||
handle_openssl_error(signature_length)
|
||||
|
||||
elif private_key.algorithm == 'rsa':
|
||||
buffer_size = libcrypto.EVP_PKEY_size(private_key.evp_pkey)
|
||||
elif pkey_is_rsa:
|
||||
buffer_size = _evp_pkey_get_size(private_key.evp_pkey)
|
||||
signature_buffer = buffer_from_bytes(buffer_size)
|
||||
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)
|
||||
|
||||
elif private_key.algorithm == 'dsa':
|
||||
elif pkey_alg == 'dsa':
|
||||
digest = getattr(hashlib, hash_algorithm)(data).digest()
|
||||
|
||||
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)
|
||||
handle_openssl_error(signature_length)
|
||||
|
||||
elif private_key.algorithm == 'ec':
|
||||
elif pkey_alg == 'ec':
|
||||
digest = getattr(hashlib, hash_algorithm)(data).digest()
|
||||
|
||||
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)
|
||||
|
||||
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_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)
|
||||
|
||||
# Use the hash algorithm output length as the salt length
|
||||
if libcrypto_version_info < (3, 0):
|
||||
res = libcrypto.EVP_PKEY_CTX_ctrl(
|
||||
evp_pkey_ctx_pointer,
|
||||
LibcryptoConst.EVP_PKEY_RSA,
|
||||
|
@ -5,7 +5,7 @@ import math
|
||||
|
||||
from .._errors import pretty_message
|
||||
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 .._types import type_name, byte_cls
|
||||
|
||||
@ -236,6 +236,9 @@ def rc4_encrypt(key, data):
|
||||
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:
|
||||
raise ValueError(pretty_message(
|
||||
'''
|
||||
@ -266,6 +269,9 @@ def rc4_decrypt(key, data):
|
||||
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:
|
||||
raise ValueError(pretty_message(
|
||||
'''
|
||||
@ -301,6 +307,9 @@ def rc2_cbc_pkcs5_encrypt(key, data, iv):
|
||||
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:
|
||||
raise ValueError(pretty_message(
|
||||
'''
|
||||
@ -345,6 +354,9 @@ def rc2_cbc_pkcs5_decrypt(key, data, iv):
|
||||
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:
|
||||
raise ValueError(pretty_message(
|
||||
'''
|
||||
@ -487,6 +499,9 @@ def des_cbc_pkcs5_encrypt(key, data, iv):
|
||||
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:
|
||||
raise ValueError(pretty_message(
|
||||
'''
|
||||
@ -530,6 +545,9 @@ def des_cbc_pkcs5_decrypt(key, data, iv):
|
||||
A byte string of the plaintext
|
||||
"""
|
||||
|
||||
if not libcrypto_legacy_support:
|
||||
raise EnvironmentError('OpenSSL has been compiled without DES support')
|
||||
|
||||
if len(key) != 8:
|
||||
raise ValueError(pretty_message(
|
||||
'''
|
||||
@ -604,23 +622,9 @@ def _encrypt(cipher, key, data, iv, padding):
|
||||
|
||||
if cipher != 'rc4' and not padding:
|
||||
# AES in CBC mode can be allowed with no padding if
|
||||
# the data is an exact multiple of the key size
|
||||
aes128_no_padding = (
|
||||
cipher == 'aes128' and
|
||||
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:
|
||||
# the data is an exact multiple of the block size
|
||||
is_aes = cipher in set(['aes128', 'aes192', 'aes256'])
|
||||
if not is_aes or (is_aes and (len(data) % 16) != 0):
|
||||
raise ValueError('padding must be specified')
|
||||
|
||||
evp_cipher_ctx = None
|
||||
@ -730,7 +734,7 @@ def _decrypt(cipher, key, data, iv, padding):
|
||||
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')
|
||||
|
||||
evp_cipher_ctx = None
|
||||
|
@ -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):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
: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:
|
||||
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_R_DH_KEY_TOO_SMALL
|
||||
)
|
||||
dh_key_info_1 = _homogenize_openssl3_error(dh_key_info_1)
|
||||
|
||||
dh_key_info_2 = (
|
||||
LibsslConst.ERR_LIB_SSL,
|
||||
LibsslConst.SSL_F_TLS_PROCESS_SKE_DHE,
|
||||
LibsslConst.SSL_R_DH_KEY_TOO_SMALL
|
||||
)
|
||||
dh_key_info_2 = _homogenize_openssl3_error(dh_key_info_2)
|
||||
|
||||
dh_key_info_3 = (
|
||||
LibsslConst.ERR_LIB_SSL,
|
||||
LibsslConst.SSL_F_SSL3_GET_KEY_EXCHANGE,
|
||||
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:
|
||||
raise_dh_params()
|
||||
|
||||
@ -541,6 +566,8 @@ class TLSSocket(object):
|
||||
LibsslConst.SSL_F_SSL3_GET_RECORD,
|
||||
LibsslConst.SSL_R_WRONG_VERSION_NUMBER
|
||||
)
|
||||
unknown_protocol_info = _homogenize_openssl3_error(unknown_protocol_info)
|
||||
|
||||
if info == unknown_protocol_info:
|
||||
raise_protocol_error(handshake_server_bytes)
|
||||
|
||||
@ -549,6 +576,7 @@ class TLSSocket(object):
|
||||
LibsslConst.SSL_F_SSL23_GET_SERVER_HELLO,
|
||||
LibsslConst.SSL_R_TLSV1_ALERT_PROTOCOL_VERSION
|
||||
)
|
||||
tls_version_info_error = _homogenize_openssl3_error(tls_version_info_error)
|
||||
if info == tls_version_info_error:
|
||||
raise_protocol_version()
|
||||
|
||||
@ -557,7 +585,9 @@ class TLSSocket(object):
|
||||
LibsslConst.SSL_F_SSL23_GET_SERVER_HELLO,
|
||||
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()
|
||||
|
||||
handshake_failure_info = (
|
||||
@ -565,6 +595,7 @@ class TLSSocket(object):
|
||||
LibsslConst.SSL_F_SSL3_READ_BYTES,
|
||||
LibsslConst.SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE
|
||||
)
|
||||
handshake_failure_info = _homogenize_openssl3_error(handshake_failure_info)
|
||||
if info == handshake_failure_info:
|
||||
saw_client_auth = False
|
||||
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_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)
|
||||
# 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_R_UNKNOWN_MESSAGE_DIGEST_ALGORITHM
|
||||
)
|
||||
unknown_hash_algo_info = _homogenize_openssl3_error(unknown_hash_algo_info)
|
||||
|
||||
if info == unknown_hash_algo_info:
|
||||
chain = extract_chain(handshake_server_bytes)
|
||||
|
@ -651,7 +651,7 @@ def raw_rsa_private_crypt(private_key, data):
|
||||
))
|
||||
|
||||
algo = private_key.asn1['private_key_algorithm']['algorithm'].native
|
||||
if algo != 'rsa':
|
||||
if algo != 'rsa' and algo != 'rsassa_pss':
|
||||
raise ValueError(pretty_message(
|
||||
'''
|
||||
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
|
||||
if algo != 'rsa':
|
||||
if algo != 'rsa' and algo != 'rsassa_pss':
|
||||
raise ValueError(pretty_message(
|
||||
'''
|
||||
certificate_or_public_key must be an RSA key, not %s
|
||||
|
@ -190,7 +190,7 @@ def pkcs12_kdf(hash_algorithm, password, salt, iterations, key_length, id_):
|
||||
|
||||
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
|
||||
to_copy = min(key_length, u)
|
||||
a = a[0:begin] + a2[0:to_copy] + a[begin + to_copy:]
|
||||
|
@ -465,6 +465,22 @@ def raise_self_signed(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):
|
||||
"""
|
||||
Raises a TLSVerificationError due to certificate being expired, or not yet
|
||||
|
@ -34,7 +34,7 @@ def open_context_handle(provider, verify_only=True):
|
||||
else:
|
||||
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
|
||||
if verify_only or provider != Advapi32Const.MS_ENH_DSS_DH_PROV:
|
||||
container_name = null()
|
||||
|
@ -567,6 +567,8 @@ class Certificate(_WinKey, _CertificateBase):
|
||||
|
||||
if signature_algo == 'rsassa_pkcs1v15':
|
||||
verify_func = rsa_pkcs1v15_verify
|
||||
elif signature_algo == 'rsassa_pss':
|
||||
verify_func = rsa_pss_verify
|
||||
elif signature_algo == 'dsa':
|
||||
verify_func = dsa_verify
|
||||
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'
|
||||
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
|
||||
else:
|
||||
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'
|
||||
algo = key_info.algorithm
|
||||
if algo == 'rsassa_pss':
|
||||
algo = 'rsa'
|
||||
|
||||
try:
|
||||
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
|
||||
"""
|
||||
|
||||
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')
|
||||
|
||||
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)
|
||||
))
|
||||
|
||||
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'])
|
||||
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'])
|
||||
|
||||
if hash_algorithm not in valid_hash_algorithms:
|
||||
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"'
|
||||
raise ValueError(pretty_message(
|
||||
'''
|
||||
@ -2413,13 +2424,13 @@ def _verify(certificate_or_public_key, signature, data, hash_algorithm, rsa_pss_
|
||||
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(
|
||||
'''
|
||||
PSS padding may only be used with RSA keys - signing via a %s key
|
||||
was requested
|
||||
''',
|
||||
certificate_or_public_key.algorithm.upper()
|
||||
cp_alg.upper()
|
||||
))
|
||||
|
||||
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_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 = {
|
||||
'sha1': 20,
|
||||
'sha224': 28,
|
||||
@ -2483,7 +2495,7 @@ def _advapi32_verify(certificate_or_public_key, signature, data, hash_algorithm,
|
||||
raise SignatureError('Signature is invalid')
|
||||
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)
|
||||
try:
|
||||
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()
|
||||
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:
|
||||
flags = BcryptConst.BCRYPT_PAD_PSS
|
||||
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
|
||||
"""
|
||||
|
||||
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')
|
||||
|
||||
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)
|
||||
))
|
||||
|
||||
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'])
|
||||
if private_key.algorithm == 'rsa' and not rsa_pss_padding:
|
||||
valid_hash_algorithms |= set(['raw'])
|
||||
|
||||
if hash_algorithm not in valid_hash_algorithms:
|
||||
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"'
|
||||
raise ValueError(pretty_message(
|
||||
'''
|
||||
@ -2813,13 +2833,13 @@ def _sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
|
||||
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(
|
||||
'''
|
||||
PSS padding may only be used with RSA keys - signing via a %s key
|
||||
was requested
|
||||
''',
|
||||
private_key.algorithm.upper()
|
||||
pkey_alg.upper()
|
||||
))
|
||||
|
||||
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_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)
|
||||
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 = {
|
||||
'sha1': 20,
|
||||
'sha224': 28,
|
||||
@ -3003,7 +3024,10 @@ def _bcrypt_sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
|
||||
padding_info = null()
|
||||
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:
|
||||
hash_length = {
|
||||
'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 = 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(
|
||||
'''
|
||||
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 = buffer_from_bytes(buffer_len)
|
||||
|
||||
if private_key.algorithm == 'rsa':
|
||||
if pkey_is_rsa:
|
||||
padding_info = cast(bcrypt, 'void *', padding_info_struct_pointer)
|
||||
|
||||
res = bcrypt.BCryptSignHash(
|
||||
@ -3072,7 +3096,7 @@ def _bcrypt_sign(private_key, data, hash_algorithm, rsa_pss_padding=False):
|
||||
handle_error(res)
|
||||
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,
|
||||
# so we have to convert it here for the verification to work
|
||||
signature = DSASignature.from_p1363(signature).dump()
|
||||
|
@ -790,8 +790,8 @@ def _encrypt(cipher, key, data, iv, padding):
|
||||
|
||||
if cipher != 'rc4' and not padding:
|
||||
# AES in CBC mode can be allowed with no padding if
|
||||
# the data is an exact multiple of the key size
|
||||
if not (cipher == 'aes' and padding is False and len(data) % len(key) == 0):
|
||||
# the data is an exact multiple of the block size
|
||||
if not (cipher == 'aes' and len(data) % 16 == 0):
|
||||
raise ValueError('padding must be specified')
|
||||
|
||||
if _backend == 'winlegacy':
|
||||
@ -1014,7 +1014,7 @@ def _decrypt(cipher, key, data, iv, padding):
|
||||
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')
|
||||
|
||||
if _backend == 'winlegacy':
|
||||
|
@ -388,7 +388,7 @@ class TLSSocket(object):
|
||||
def __init__(self, address, port, timeout=10, session=None):
|
||||
"""
|
||||
: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:
|
||||
An integer of the port number to connect to
|
||||
|
@ -2,5 +2,5 @@
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
|
||||
__version__ = '1.2.1'
|
||||
__version_info__ = (1, 2, 1)
|
||||
__version__ = '1.3.0'
|
||||
__version_info__ = (1, 3, 0)
|
||||
|
@ -1,8 +1,20 @@
|
||||
# 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
|
||||
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)
|
||||
- [Features](#features)
|
||||
@ -19,8 +31,6 @@ on Windows, OS X, Linux and BSD. Supports the following versions of Python:
|
||||
- [CI Tasks](#ci-tasks)
|
||||
|
||||
[![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)
|
||||
[![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.14
|
||||
- macOS 10.15
|
||||
- macOS 10.15 with OpenSSL 3.0
|
||||
- macOS 11
|
||||
- macOS 12
|
||||
- Linux or BSD
|
||||
- Uses one of:
|
||||
- [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.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/)
|
||||
- Tested on:
|
||||
- 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 16.04 with OpenSSL 1.0.2 on Raspberry Pi 3 (armhf)
|
||||
- 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
|
||||
cryptographic primitives and due to lack of vendor support.*
|
||||
@ -199,7 +213,10 @@ Some downsides include:
|
||||
## Dependencies
|
||||
|
||||
- [*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
|
||||
|
||||
@ -220,10 +237,8 @@ pip install oscrypto
|
||||
|
||||
Various combinations of platforms and versions of Python are tested via:
|
||||
|
||||
- [AppVeyor](https://ci.appveyor.com/project/wbond/oscrypto/history)
|
||||
- [CircleCI](https://circleci.com/gh/wbond/oscrypto)
|
||||
- [GitHub Actions](https://github.com/wbond/oscrypto/actions)
|
||||
- [Travis CI](https://travis-ci.org/wbond/oscrypto/builds)
|
||||
- [macOS, Linux, Windows](https://github.com/wbond/oscrypto/actions/workflows/ci.yml) via GitHub Actions
|
||||
- [arm64](https://circleci.com/gh/wbond/oscrypto) via CircleCI
|
||||
|
||||
## Testing
|
||||
|
||||
@ -256,11 +271,36 @@ python run.py tests 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
|
||||
Mac, add `use_openssl` after `run.py`, like:
|
||||
|
||||
```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
|
||||
@ -272,6 +312,45 @@ PyPi, the full test suite is run via:
|
||||
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
|
||||
|
||||
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
|
||||
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).
|
||||
If the current directory is a clean git working copy, the coverage data is
|
||||
submitted to codecov.io.
|
||||
|
@ -10,7 +10,7 @@ from setuptools.command.egg_info import egg_info
|
||||
|
||||
|
||||
PACKAGE_NAME = 'oscrypto'
|
||||
PACKAGE_VERSION = '1.2.1'
|
||||
PACKAGE_VERSION = '1.3.0'
|
||||
PACKAGE_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
@ -126,8 +126,10 @@ setup(
|
||||
|
||||
'License :: OSI Approved :: MIT License',
|
||||
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
@ -135,6 +137,8 @@ setup(
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
|
||||
'Topic :: Security :: Cryptography',
|
||||
@ -142,7 +146,7 @@ setup(
|
||||
|
||||
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*']),
|
||||
package_data=package_data,
|
||||
|
@ -20,6 +20,15 @@
|
||||
"singleinstance": false
|
||||
},
|
||||
"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": [
|
||||
"Magica Quartet, Hanokage"
|
||||
|
@ -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"}]
|
@ -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"}]
|
@ -0,0 +1 @@
|
||||
[]
|
@ -0,0 +1 @@
|
||||
[]
|
Loading…
Reference in New Issue
Block a user