http-message-signatures 0.4.4__tar.gz → 0.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. http_message_signatures-0.6.1/.github/workflows/ci.yml +38 -0
  2. http_message_signatures-0.6.1/.github/workflows/release.yml +21 -0
  3. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/.gitignore +1 -1
  4. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/Changes.rst +18 -0
  5. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/LICENSE +0 -24
  6. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/Makefile +1 -1
  7. http_message_signatures-0.6.1/NOTICE +10 -0
  8. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/PKG-INFO +41 -16
  9. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/README.rst +19 -8
  10. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/common.mk +4 -18
  11. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/docs/conf.py +1 -1
  12. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/http_message_signatures/__init__.py +2 -1
  13. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/http_message_signatures/_algorithms.py +17 -19
  14. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/http_message_signatures/exceptions.py +1 -1
  15. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/http_message_signatures/resolvers.py +3 -3
  16. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/http_message_signatures/signatures.py +47 -41
  17. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/http_message_signatures/structures.py +1 -5
  18. http_message_signatures-0.6.1/pyproject.toml +54 -0
  19. http_message_signatures-0.6.1/setup.cfg +3 -0
  20. http_message_signatures-0.6.1/test/test.py +346 -0
  21. http-message-signatures-0.4.4/.github/workflows/ci.yml +0 -26
  22. http-message-signatures-0.4.4/http_message_signatures/version.py +0 -5
  23. http-message-signatures-0.4.4/http_message_signatures.egg-info/PKG-INFO +0 -109
  24. http-message-signatures-0.4.4/http_message_signatures.egg-info/SOURCES.txt +0 -35
  25. http-message-signatures-0.4.4/http_message_signatures.egg-info/dependency_links.txt +0 -1
  26. http-message-signatures-0.4.4/http_message_signatures.egg-info/requires.txt +0 -10
  27. http-message-signatures-0.4.4/http_message_signatures.egg-info/top_level.txt +0 -1
  28. http-message-signatures-0.4.4/setup.cfg +0 -8
  29. http-message-signatures-0.4.4/setup.py +0 -50
  30. http-message-signatures-0.4.4/test/test.py +0 -243
  31. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/.github/FUNDING.yml +0 -0
  32. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/docs/index.rst +0 -0
  33. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/http_message_signatures/algorithms.py +0 -0
  34. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/http_message_signatures/py.typed +0 -0
  35. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/test/test-key-ecc-p256.key +0 -0
  36. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/test/test-key-ecc-p256.pem +0 -0
  37. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/test/test-key-ed25519.key +0 -0
  38. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/test/test-key-ed25519.pem +0 -0
  39. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/test/test-key-rsa-pss.key +0 -0
  40. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/test/test-key-rsa-pss.pem +0 -0
  41. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/test/test-key-rsa.key +0 -0
  42. {http-message-signatures-0.4.4 → http_message_signatures-0.6.1}/test/test-key-rsa.pem +0 -0
@@ -0,0 +1,38 @@
1
+ name: Test
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ CI:
7
+ name: Python ${{ matrix.python-version }}
8
+ runs-on: ubuntu-22.04
9
+ strategy:
10
+ fail-fast: false
11
+ max-parallel: 8
12
+ matrix:
13
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+ - name: Install build dependencies
21
+ run: pip install build wheel
22
+ - name: Install package
23
+ run: pip install .[tests]
24
+ - name: Test
25
+ run: make test
26
+ isort:
27
+ runs-on: ubuntu-22.04
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+ - uses: isort/isort-action@v1.1.0
31
+ ruff:
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+ - uses: astral-sh/ruff-action@v3
36
+ - uses: astral-sh/ruff-action@v3
37
+ with:
38
+ args: "format --check"
@@ -0,0 +1,21 @@
1
+ name: Publish release to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v[0-9]+.[0-9]+.[0-9]+'
7
+
8
+ jobs:
9
+ pypi-publish:
10
+ name: Build and upload release to PyPI
11
+ runs-on: ubuntu-latest
12
+ environment: release
13
+ permissions:
14
+ id-token: write
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
+ - run: pip install build
19
+ - run: python -m build
20
+ - name: Publish package distributions to PyPI
21
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -10,7 +10,7 @@ __pycache__/
10
10
  /build/
11
11
  /dist/
12
12
  /.eggs/
13
- /yq/version.py
13
+ /*/version.py
14
14
 
15
15
  # IDE project files
16
16
  /.pydevproject
@@ -1,3 +1,21 @@
1
+ Changes for v0.6.1 (2025-05-29)
2
+ ===============================
3
+
4
+ - Switch to trusted publishing
5
+
6
+ Changes for v0.6.0 (2025-05-29)
7
+ ===============================
8
+
9
+ - [http-message-signatures-add] Add support for ‘tag’ signature
10
+ parameter (#17)
11
+
12
+ Changes for v0.5.0 (2024-02-21)
13
+ ===============================
14
+
15
+ - Add max_clock_skew to HTTPMessageVerifier (#11)
16
+
17
+ - Documentation and test infrastructure improvements
18
+
1
19
  Changes for v0.4.4 (2022-08-31)
2
20
  ===============================
3
21
 
@@ -165,27 +165,3 @@ incurred by, or claims asserted against, such Contributor by reason of your
165
165
  accepting any such warranty or additional liability.
166
166
 
167
167
  END OF TERMS AND CONDITIONS
168
-
169
- APPENDIX: How to apply the Apache License to your work
170
-
171
- To apply the Apache License to your work, attach the following boilerplate
172
- notice, with the fields enclosed by brackets "[]" replaced with your own
173
- identifying information. (Don't include the brackets!) The text should be
174
- enclosed in the appropriate comment syntax for the file format. We also
175
- recommend that a file or class name and description of purpose be included on
176
- the same "printed page" as the copyright notice for easier identification within
177
- third-party archives.
178
-
179
- Copyright [yyyy] [name of copyright owner]
180
-
181
- Licensed under the Apache License, Version 2.0 (the "License");
182
- you may not use this file except in compliance with the License.
183
- You may obtain a copy of the License at
184
-
185
- http://www.apache.org/licenses/LICENSE-2.0
186
-
187
- Unless required by applicable law or agreed to in writing, software
188
- distributed under the License is distributed on an "AS IS" BASIS,
189
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190
- See the License for the specific language governing permissions and
191
- limitations under the License.
@@ -1,7 +1,7 @@
1
1
  SHELL=/bin/bash
2
2
 
3
3
  lint:
4
- flake8
4
+ ruff check http_message_signatures
5
5
  mypy --check-untyped-defs http_message_signatures
6
6
 
7
7
  test: lint
@@ -0,0 +1,10 @@
1
+ http-message-signatures is a free open source implementation of the
2
+ IETF HTTP Message Signatures standard, RFC 9421. This project is
3
+ staffed by volunteers. If you are using http-message-signatures in a
4
+ for-profit project, please contribute to its development and
5
+ maintenance using the "Sponsor" button on the GitHub project page,
6
+ https://github.com/pyauth/http-message-signatures. If you are looking
7
+ for support with commercial applications based on
8
+ http-message-signatures, please donate and contact its developers
9
+ using the issue tracker on the http-message-signatures project page or
10
+ the contact information listed in README.rst.
@@ -1,31 +1,45 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: http-message-signatures
3
- Version: 0.4.4
3
+ Version: 0.6.1
4
4
  Summary: An implementation of the IETF HTTP Message Signatures draft standard
5
- Home-page: https://github.com/pyauth/http-message-signatures
5
+ Project-URL: Homepage, https://github.com/pyauth/http-message-signatures
6
6
  Author: Andrey Kislyuk
7
7
  Author-email: kislyuk@gmail.com
8
+ Maintainer: Andrey Kislyuk
9
+ Maintainer-email: kislyuk@gmail.com
8
10
  License: Apache Software License
9
- Platform: MacOS X
10
- Platform: Posix
11
+ License-File: LICENSE
12
+ License-File: NOTICE
11
13
  Classifier: Intended Audience :: Developers
12
14
  Classifier: License :: OSI Approved :: Apache Software License
13
15
  Classifier: Operating System :: MacOS :: MacOS X
14
16
  Classifier: Operating System :: POSIX
15
17
  Classifier: Programming Language :: Python
16
- Classifier: Programming Language :: Python :: 3.7
17
18
  Classifier: Programming Language :: Python :: 3.8
18
19
  Classifier: Programming Language :: Python :: 3.9
19
20
  Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
20
24
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Requires-Python: >=3.8
26
+ Requires-Dist: cryptography>=36.0.2
27
+ Requires-Dist: http-sfv>=0.9.3
21
28
  Provides-Extra: tests
22
- License-File: LICENSE
23
-
24
- http-message-signatures: An implementation of the IETF HTTP Message Signatures draft standard
25
- =============================================================================================
29
+ Requires-Dist: build; extra == 'tests'
30
+ Requires-Dist: coverage; extra == 'tests'
31
+ Requires-Dist: flake8; extra == 'tests'
32
+ Requires-Dist: mypy; extra == 'tests'
33
+ Requires-Dist: requests; extra == 'tests'
34
+ Requires-Dist: ruff; extra == 'tests'
35
+ Requires-Dist: wheel; extra == 'tests'
36
+ Description-Content-Type: text/x-rst
37
+
38
+ http-message-signatures: An implementation of RFC 9421, the IETF HTTP Message Signatures standard
39
+ =================================================================================================
26
40
 
27
41
  *http-message-signatures* is an implementation of the IETF
28
- `HTTP Message Signatures <https://datatracker.ietf.org/doc/draft-ietf-httpbis-message-signatures>`_ draft standard in
42
+ `RFC 9421 HTTP Message Signatures <https://datatracker.ietf.org/doc/rfc9421/>`_ standard in
29
43
  Python.
30
44
 
31
45
  Installation
@@ -76,9 +90,9 @@ builds upon this package to provide integrated signing and validation of the req
76
90
  In http-message-signatures, you can ensure that the information signed is what you expect to be signed by only trusting the
77
91
  data returned by the ``verify()`` method::
78
92
 
79
- verify_result = verifier.verify(request)
93
+ verify_results = verifier.verify(request)
80
94
 
81
- This returns VerifyResult, a namedtuple with the following attributes:
95
+ This returns a list of ``VerifyResult`` s, which are ``namedtuple`` s with the following attributes:
82
96
 
83
97
  * label (str): The label for the signature
84
98
  * algorithm: (same as signature_algorithm above)
@@ -87,9 +101,17 @@ builds upon this package to provide integrated signing and validation of the req
87
101
  * body: Always ``None`` (the `requests-http-signature <https://github.com/pyauth/requests-http-signature>`_ package
88
102
  implements returning the body upon successful digest validation).
89
103
 
104
+ Given an HTTP request can potentially have multiple signatures the ``verify()`` method returns a list of ``VerifyResult`` s.
105
+ However, the implementation currently supports just one signature, so the returned list currently contains just one element.
106
+ If more signatures are found in the request then ``InvalidSignature`` is raised.
107
+
108
+ Additionally, the ``verify()`` method raises ``HTTPMessageSignaturesException`` or an exception derived from this class in
109
+ case an error occurs (unable to load PEM key, unsupported algorithm specified in signature input, signature doesn't match
110
+ digest etc.)
111
+
90
112
  Authors
91
113
  -------
92
- * Andrey Kislyuk
114
+ * `Andrey Kislyuk <https://kislyuk.com>`
93
115
 
94
116
  Links
95
117
  -----
@@ -97,7 +119,7 @@ Links
97
119
  * `Documentation <https://FIXME>`_
98
120
  * `Package distribution (PyPI) <https://pypi.python.org/pypi/http-message-signatures>`_
99
121
  * `Change log <https://github.com/pyauth/http-message-signatures/blob/master/Changes.rst>`_
100
- * `IETF HTTP Message Signatures standard tracker <https://datatracker.ietf.org/doc/draft-ietf-httpbis-message-signatures/>`_
122
+ * `IETF HTTP Message Signatures standard tracker <https://datatracker.ietf.org/doc/rfc9421/>`_
101
123
  * `OWASP Top Ten <https://owasp.org/www-project-top-ten/>`_
102
124
 
103
125
  Bugs
@@ -106,4 +128,7 @@ Please report bugs, issues, feature requests, etc. on `GitHub <https://github.co
106
128
 
107
129
  License
108
130
  -------
109
- Licensed under the terms of the `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_.
131
+ Copyright 2017-2024, Andrey Kislyuk and http-message-signatures contributors. Licensed under the terms of the
132
+ `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of attribution information,
133
+ LICENSE and NOTICE files with source copies of this package and derivative works is **REQUIRED** as specified by the
134
+ Apache License.
@@ -1,8 +1,8 @@
1
- http-message-signatures: An implementation of the IETF HTTP Message Signatures draft standard
2
- =============================================================================================
1
+ http-message-signatures: An implementation of RFC 9421, the IETF HTTP Message Signatures standard
2
+ =================================================================================================
3
3
 
4
4
  *http-message-signatures* is an implementation of the IETF
5
- `HTTP Message Signatures <https://datatracker.ietf.org/doc/draft-ietf-httpbis-message-signatures>`_ draft standard in
5
+ `RFC 9421 HTTP Message Signatures <https://datatracker.ietf.org/doc/rfc9421/>`_ standard in
6
6
  Python.
7
7
 
8
8
  Installation
@@ -53,9 +53,9 @@ builds upon this package to provide integrated signing and validation of the req
53
53
  In http-message-signatures, you can ensure that the information signed is what you expect to be signed by only trusting the
54
54
  data returned by the ``verify()`` method::
55
55
 
56
- verify_result = verifier.verify(request)
56
+ verify_results = verifier.verify(request)
57
57
 
58
- This returns VerifyResult, a namedtuple with the following attributes:
58
+ This returns a list of ``VerifyResult`` s, which are ``namedtuple`` s with the following attributes:
59
59
 
60
60
  * label (str): The label for the signature
61
61
  * algorithm: (same as signature_algorithm above)
@@ -64,9 +64,17 @@ builds upon this package to provide integrated signing and validation of the req
64
64
  * body: Always ``None`` (the `requests-http-signature <https://github.com/pyauth/requests-http-signature>`_ package
65
65
  implements returning the body upon successful digest validation).
66
66
 
67
+ Given an HTTP request can potentially have multiple signatures the ``verify()`` method returns a list of ``VerifyResult`` s.
68
+ However, the implementation currently supports just one signature, so the returned list currently contains just one element.
69
+ If more signatures are found in the request then ``InvalidSignature`` is raised.
70
+
71
+ Additionally, the ``verify()`` method raises ``HTTPMessageSignaturesException`` or an exception derived from this class in
72
+ case an error occurs (unable to load PEM key, unsupported algorithm specified in signature input, signature doesn't match
73
+ digest etc.)
74
+
67
75
  Authors
68
76
  -------
69
- * Andrey Kislyuk
77
+ * `Andrey Kislyuk <https://kislyuk.com>`
70
78
 
71
79
  Links
72
80
  -----
@@ -74,7 +82,7 @@ Links
74
82
  * `Documentation <https://FIXME>`_
75
83
  * `Package distribution (PyPI) <https://pypi.python.org/pypi/http-message-signatures>`_
76
84
  * `Change log <https://github.com/pyauth/http-message-signatures/blob/master/Changes.rst>`_
77
- * `IETF HTTP Message Signatures standard tracker <https://datatracker.ietf.org/doc/draft-ietf-httpbis-message-signatures/>`_
85
+ * `IETF HTTP Message Signatures standard tracker <https://datatracker.ietf.org/doc/rfc9421/>`_
78
86
  * `OWASP Top Ten <https://owasp.org/www-project-top-ten/>`_
79
87
 
80
88
  Bugs
@@ -83,4 +91,7 @@ Please report bugs, issues, feature requests, etc. on `GitHub <https://github.co
83
91
 
84
92
  License
85
93
  -------
86
- Licensed under the terms of the `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_.
94
+ Copyright 2017-2024, Andrey Kislyuk and http-message-signatures contributors. Licensed under the terms of the
95
+ `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of attribution information,
96
+ LICENSE and NOTICE files with source copies of this package and derivative works is **REQUIRED** as specified by the
97
+ Apache License.
@@ -17,15 +17,9 @@ release:
17
17
  @if [[ -z $$TAG ]]; then echo "Use release-{major,minor,patch}"; exit 1; fi
18
18
  @if ! type -P pandoc; then echo "Please install pandoc"; exit 1; fi
19
19
  @if ! type -P sponge; then echo "Please install moreutils"; exit 1; fi
20
- @if ! type -P http; then echo "Please install httpie"; exit 1; fi
21
- @if ! type -P twine; then echo "Please install twine"; exit 1; fi
22
- $(eval REMOTE=$(shell git remote get-url origin | perl -ne '/([^\/\:]+\/[^\/\:]+?)(\.git)?$$/; print $$1'))
23
- $(eval GIT_USER=$(shell git config --get user.email))
24
- $(eval GH_AUTH=$(shell if grep -q '@github.com' ~/.git-credentials; then echo $$(grep '@github.com' ~/.git-credentials | python3 -c 'import sys, urllib.parse as p; print(p.urlparse(sys.stdin.read()).netloc.split("@")[0])'); else echo $(GIT_USER); fi))
25
- $(eval RELEASES_API=https://api.github.com/repos/${REMOTE}/releases)
26
- $(eval UPLOADS_API=https://uploads.github.com/repos/${REMOTE}/releases)
20
+ @if ! type -P gh; then echo "Please install gh"; exit 1; fi
27
21
  git pull
28
- git clean -x --force $$(python setup.py --name)
22
+ git clean -x --force $$(ls */__init__.py | xargs dirname)
29
23
  TAG_MSG=$$(mktemp); \
30
24
  echo "# Changes for ${TAG} ($$(date +%Y-%m-%d))" > $$TAG_MSG; \
31
25
  git log --pretty=format:%s $$(git describe --abbrev=0)..HEAD >> $$TAG_MSG; \
@@ -33,17 +27,9 @@ release:
33
27
  if [[ -f Changes.md ]]; then cat $$TAG_MSG <(echo) Changes.md | sponge Changes.md; git add Changes.md; fi; \
34
28
  if [[ -f Changes.rst ]]; then cat <(pandoc --from markdown --to rst $$TAG_MSG) <(echo) Changes.rst | sponge Changes.rst; git add Changes.rst; fi; \
35
29
  git commit -m ${TAG}; \
36
- git tag --sign --annotate --file $$TAG_MSG ${TAG}
30
+ git tag --annotate --file $$TAG_MSG ${TAG}
37
31
  git push --follow-tags
38
- http --check-status --auth ${GH_AUTH} ${RELEASES_API} tag_name=${TAG} name=${TAG} \
39
- body="$$(git tag --list ${TAG} -n99 | perl -pe 's/^\S+\s*// if $$. == 1' | sed 's/^\s\s\s\s//')"
40
32
  $(MAKE) install
41
- http --check-status --auth ${GH_AUTH} POST ${UPLOADS_API}/$$(http --auth ${GH_AUTH} ${RELEASES_API}/latest | jq .id)/assets \
42
- name==$$(basename dist/*.whl) label=="Python Wheel" < dist/*.whl
43
- $(MAKE) release-pypi
44
-
45
- release-pypi:
46
- python -m build
47
- twine upload dist/*.tar.gz dist/*.whl --sign --verbose
33
+ gh release create ${TAG} dist/*.whl --notes="$$(git tag --list ${TAG} -n99 | perl -pe 's/^\S+\s*// if $$. == 1' | sed 's/^\s\s\s\s//')"
48
34
 
49
35
  .PHONY: release
@@ -24,6 +24,6 @@ html_sidebars = {
24
24
  "logo-text.html",
25
25
  # "globaltoc.html",
26
26
  "localtoc.html",
27
- "searchbox.html"
27
+ "searchbox.html",
28
28
  ]
29
29
  }
@@ -1,6 +1,7 @@
1
1
  from . import algorithms # noqa:F401
2
2
  from .algorithms import HTTPSignatureAlgorithm # noqa:F401
3
3
  from .exceptions import HTTPMessageSignaturesException, InvalidSignature # noqa:F401
4
- from .resolvers import HTTPSignatureComponentResolver, HTTPSignatureKeyResolver # noqa:F401
4
+ from .resolvers import HTTPSignatureComponentResolver # noqa:F401
5
+ from .resolvers import HTTPSignatureKeyResolver # noqa: F401
5
6
  from .signatures import HTTPMessageSigner, HTTPMessageVerifier # noqa:F401
6
7
  from .structures import VerifyResult # noqa:F401
@@ -1,7 +1,13 @@
1
1
  from cryptography.hazmat.primitives import hashes, hmac
2
2
  from cryptography.hazmat.primitives.asymmetric import ec, ed25519, padding, rsa
3
- from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature
4
- from cryptography.hazmat.primitives.serialization import load_pem_private_key, load_pem_public_key
3
+ from cryptography.hazmat.primitives.asymmetric.utils import (
4
+ decode_dss_signature,
5
+ encode_dss_signature,
6
+ )
7
+ from cryptography.hazmat.primitives.serialization import (
8
+ load_pem_private_key,
9
+ load_pem_public_key,
10
+ )
5
11
 
6
12
  from .exceptions import HTTPMessageSignaturesException
7
13
 
@@ -41,15 +47,10 @@ class RSA_PSS_SHA512(HTTPSignatureAlgorithm, PEMKeyLoader):
41
47
  self.hash_algorithm: hashes.HashAlgorithm = hashes.SHA512()
42
48
 
43
49
  def sign(self, message: bytes):
44
- return self.private_key.sign(data=message,
45
- padding=self.padding,
46
- algorithm=self.hash_algorithm)
50
+ return self.private_key.sign(data=message, padding=self.padding, algorithm=self.hash_algorithm)
47
51
 
48
52
  def verify(self, signature: bytes, message: bytes):
49
- self.public_key.verify(signature=signature,
50
- data=message,
51
- padding=self.padding,
52
- algorithm=self.hash_algorithm)
53
+ self.public_key.verify(signature=signature, data=message, padding=self.padding, algorithm=self.hash_algorithm)
53
54
 
54
55
 
55
56
  class RSA_V1_5_SHA256(RSA_PSS_SHA512):
@@ -90,27 +91,24 @@ class ECDSA_P256_SHA256(HTTPSignatureAlgorithm, PEMKeyLoader):
90
91
  raise HTTPMessageSignaturesException("Unexpected public key type")
91
92
  if self.private_key and not isinstance(self.private_key, ec.EllipticCurvePrivateKey):
92
93
  raise HTTPMessageSignaturesException("Unexpected private key type")
93
- if self.public_key and type(self.public_key.curve) != ec.SECP256R1:
94
+ if self.public_key and not isinstance(self.public_key.curve, ec.SECP256R1):
94
95
  raise HTTPMessageSignaturesException("Unexpected elliptic curve type in public key")
95
- if self.private_key and type(self.private_key.curve) != ec.SECP256R1:
96
+ if self.private_key and not isinstance(self.private_key.curve, ec.SECP256R1):
96
97
  raise HTTPMessageSignaturesException("Unexpected elliptic curve type in private key")
97
98
  self.signature_algorithm = ec.ECDSA(hashes.SHA256())
98
99
 
99
100
  def sign(self, message: bytes):
100
- der_sig = self.private_key.sign(message,
101
- signature_algorithm=self.signature_algorithm)
101
+ der_sig = self.private_key.sign(message, signature_algorithm=self.signature_algorithm)
102
102
  r, s = decode_dss_signature(der_sig)
103
- return r.to_bytes(32, byteorder='big') + s.to_bytes(32, byteorder='big')
103
+ return r.to_bytes(32, byteorder="big") + s.to_bytes(32, byteorder="big")
104
104
 
105
105
  def verify(self, signature: bytes, message: bytes):
106
106
  if len(signature) != 64:
107
107
  raise HTTPMessageSignaturesException("Unexpected signature length")
108
- r = int.from_bytes(signature[:32], byteorder='big')
109
- s = int.from_bytes(signature[32:], byteorder='big')
108
+ r = int.from_bytes(signature[:32], byteorder="big")
109
+ s = int.from_bytes(signature[32:], byteorder="big")
110
110
  der_sig = encode_dss_signature(r, s)
111
- self.public_key.verify(signature=der_sig,
112
- data=message,
113
- signature_algorithm=self.signature_algorithm)
111
+ self.public_key.verify(signature=der_sig, data=message, signature_algorithm=self.signature_algorithm)
114
112
 
115
113
 
116
114
  class ED25519(HTTPSignatureAlgorithm, PEMKeyLoader):
@@ -2,5 +2,5 @@ class HTTPMessageSignaturesException(Exception):
2
2
  "Base class for exceptions raised by http_message_signatures"
3
3
 
4
4
 
5
- class InvalidSignature(Exception):
5
+ class InvalidSignature(HTTPMessageSignaturesException):
6
6
  "Class for exceptions raised in the course of verifying an HTTP message signature"
@@ -17,7 +17,7 @@ class HTTPSignatureComponentResolver:
17
17
  "@query",
18
18
  "@query-params",
19
19
  "@status",
20
- "@request-response"
20
+ "@request-response",
21
21
  }
22
22
 
23
23
  # TODO: describe interface
@@ -33,7 +33,7 @@ class HTTPSignatureComponentResolver:
33
33
  component_id = str(component_node.value)
34
34
  if component_id.startswith("@"): # derived component
35
35
  if component_id not in self.derived_component_names:
36
- raise HTTPMessageSignaturesException(f'Unknown covered derived component name {component_id}')
36
+ raise HTTPMessageSignaturesException(f"Unknown covered derived component name {component_id}")
37
37
  resolver = getattr(self, "get_" + component_id[1:].replace("-", "_"))
38
38
  return resolver(**component_node.params)
39
39
  if component_id not in self.headers:
@@ -68,7 +68,7 @@ class HTTPSignatureComponentResolver:
68
68
  if name not in query:
69
69
  raise HTTPMessageSignaturesException(f'Query parameter "{name}" not found in the message URL')
70
70
  if len(query[name]) != 1:
71
- raise HTTPMessageSignaturesException('Query parameters with multiple values are not supported.')
71
+ raise HTTPMessageSignaturesException("Query parameters with multiple values are not supported.")
72
72
  return query[name][0]
73
73
 
74
74
  def get_status(self):
@@ -1,7 +1,7 @@
1
1
  import collections
2
2
  import datetime
3
3
  import logging
4
- from typing import Any, Dict, List, Sequence, Tuple, Type
4
+ from typing import Any, Dict, List, Optional, Sequence, Tuple, Type
5
5
 
6
6
  import http_sfv
7
7
 
@@ -14,27 +14,24 @@ logger = logging.getLogger(__name__)
14
14
 
15
15
 
16
16
  class HTTPSignatureHandler:
17
- signature_metadata_parameters = {
18
- "alg",
19
- "created",
20
- "expires",
21
- "keyid",
22
- "nonce"
23
- }
24
-
25
- def __init__(self, *,
26
- signature_algorithm: Type[HTTPSignatureAlgorithm],
27
- key_resolver: HTTPSignatureKeyResolver,
28
- component_resolver_class: type = HTTPSignatureComponentResolver):
17
+ signature_metadata_parameters = {"alg", "created", "expires", "keyid", "nonce", "tag"}
18
+
19
+ def __init__(
20
+ self,
21
+ *,
22
+ signature_algorithm: Type[HTTPSignatureAlgorithm],
23
+ key_resolver: HTTPSignatureKeyResolver,
24
+ component_resolver_class: type = HTTPSignatureComponentResolver,
25
+ ):
29
26
  if signature_algorithm not in signature_algorithms.values():
30
27
  raise HTTPMessageSignaturesException(f"Unknown signature algorithm {signature_algorithm}")
31
28
  self.signature_algorithm = signature_algorithm
32
29
  self.key_resolver = key_resolver
33
30
  self.component_resolver_class = component_resolver_class
34
31
 
35
- def _build_signature_base(self, message, *,
36
- covered_component_ids: List[Any],
37
- signature_params: Dict[str, str]) -> Tuple:
32
+ def _build_signature_base(
33
+ self, message, *, covered_component_ids: List[Any], signature_params: Dict[str, str]
34
+ ) -> Tuple:
38
35
  assert "@signature-params" not in covered_component_ids
39
36
  sig_elements = collections.OrderedDict()
40
37
  component_resolver = self.component_resolver_class(message)
@@ -48,8 +45,9 @@ class HTTPSignatureHandler:
48
45
  if "\n" in component_key:
49
46
  raise HTTPMessageSignaturesException(f'Component ID "{component_key}" contains newline character')
50
47
  if component_key in sig_elements:
51
- raise HTTPMessageSignaturesException(f'Component ID "{component_key}" appeared multiple times in '
52
- 'signature input')
48
+ raise HTTPMessageSignaturesException(
49
+ f'Component ID "{component_key}" appeared multiple times in signature input'
50
+ )
53
51
  sig_elements[component_key] = component_value
54
52
  sig_params_node = http_sfv.InnerList(covered_component_ids)
55
53
  sig_params_node.params.update(signature_params)
@@ -72,14 +70,19 @@ class HTTPMessageSigner(HTTPSignatureHandler):
72
70
  covered_component_nodes.append(component_name_node)
73
71
  return covered_component_nodes
74
72
 
75
- def sign(self, message, *,
76
- key_id: str,
77
- created: datetime.datetime = None,
78
- expires: datetime.datetime = None,
79
- nonce: str = None,
80
- label: str = None,
81
- include_alg: bool = True,
82
- covered_component_ids: Sequence[str] = ("@method", "@authority", "@target-uri")):
73
+ def sign(
74
+ self,
75
+ message,
76
+ *,
77
+ key_id: str,
78
+ created: Optional[datetime.datetime] = None,
79
+ expires: Optional[datetime.datetime] = None,
80
+ nonce: Optional[str] = None,
81
+ label: Optional[str] = None,
82
+ tag: Optional[str] = None,
83
+ include_alg: bool = True,
84
+ covered_component_ids: Sequence[str] = ("@method", "@authority", "@target-uri"),
85
+ ):
83
86
  # TODO: Accept-Signature autonegotiation
84
87
  key = self.key_resolver.resolve_private_key(key_id)
85
88
  if created is None:
@@ -91,13 +94,13 @@ class HTTPMessageSigner(HTTPSignatureHandler):
91
94
  signature_params["expires"] = int(expires.timestamp())
92
95
  if nonce:
93
96
  signature_params["nonce"] = nonce
97
+ if tag:
98
+ signature_params["tag"] = tag
94
99
  if include_alg:
95
100
  signature_params["alg"] = self.signature_algorithm.algorithm_id
96
101
  covered_component_nodes = self._parse_covered_component_ids(covered_component_ids)
97
102
  sig_base, sig_params_node, _ = self._build_signature_base(
98
- message,
99
- covered_component_ids=covered_component_nodes,
100
- signature_params=signature_params
103
+ message, covered_component_ids=covered_component_nodes, signature_params=signature_params
101
104
  )
102
105
  signer = self.signature_algorithm(private_key=key)
103
106
  signature = signer.sign(sig_base.encode())
@@ -111,6 +114,7 @@ class HTTPMessageSigner(HTTPSignatureHandler):
111
114
 
112
115
 
113
116
  class HTTPMessageVerifier(HTTPSignatureHandler):
117
+ max_clock_skew: datetime.timedelta = datetime.timedelta(seconds=5)
114
118
  require_created: bool = True
115
119
 
116
120
  def _parse_dict_header(self, header_name, headers):
@@ -133,17 +137,19 @@ class HTTPMessageVerifier(HTTPSignatureHandler):
133
137
 
134
138
  def validate_created_and_expires(self, sig_input, max_age=None):
135
139
  now = datetime.datetime.now()
140
+ min_time = now - self.max_clock_skew
141
+ max_time = now + self.max_clock_skew
136
142
  if "created" in sig_input.params:
137
- if self._parse_integer_timestamp(sig_input.params["created"], field_name="created") > now:
143
+ if self._parse_integer_timestamp(sig_input.params["created"], field_name="created") > max_time:
138
144
  raise InvalidSignature('Signature "created" parameter is set to a time in the future')
139
145
  elif self.require_created:
140
146
  raise InvalidSignature('Signature is missing a required "created" parameter')
141
147
  if "expires" in sig_input.params:
142
- if self._parse_integer_timestamp(sig_input.params["expires"], field_name="expires") < now:
148
+ if self._parse_integer_timestamp(sig_input.params["expires"], field_name="expires") < min_time:
143
149
  raise InvalidSignature('Signature "expires" parameter is set to a time in the past')
144
150
  if max_age is not None:
145
- if self._parse_integer_timestamp(sig_input.params["created"], field_name="created") + max_age < now:
146
- raise InvalidSignature(f'Signature age exceeds maximum allowable age {max_age}')
151
+ if self._parse_integer_timestamp(sig_input.params["created"], field_name="created") + max_age < min_time:
152
+ raise InvalidSignature(f"Signature age exceeds maximum allowable age {max_age}")
147
153
 
148
154
  def verify(self, message, *, max_age: datetime.timedelta = datetime.timedelta(days=1)) -> List[VerifyResult]:
149
155
  sig_inputs = self._parse_dict_header("Signature-Input", message.headers)
@@ -165,9 +171,7 @@ class HTTPMessageVerifier(HTTPSignatureHandler):
165
171
  raise InvalidSignature(f'Unexpected signature metadata parameter "{param}"')
166
172
  try:
167
173
  sig_base, sig_params_node, sig_elements = self._build_signature_base(
168
- message,
169
- covered_component_ids=list(sig_input),
170
- signature_params=sig_input.params
174
+ message, covered_component_ids=list(sig_input), signature_params=sig_input.params
171
175
  )
172
176
  except Exception as e:
173
177
  raise InvalidSignature(e) from e
@@ -177,10 +181,12 @@ class HTTPMessageVerifier(HTTPSignatureHandler):
177
181
  verifier.verify(signature=raw_signature, message=sig_base.encode())
178
182
  except Exception as e:
179
183
  raise InvalidSignature(e) from e
180
- verify_result = VerifyResult(label=label,
181
- algorithm=self.signature_algorithm,
182
- covered_components=sig_elements,
183
- parameters=dict(sig_params_node.params),
184
- body=None)
184
+ verify_result = VerifyResult(
185
+ label=label,
186
+ algorithm=self.signature_algorithm,
187
+ covered_components=sig_elements,
188
+ parameters=dict(sig_params_node.params),
189
+ body=None,
190
+ )
185
191
  verify_results.append(verify_result)
186
192
  return verify_results
@@ -34,11 +34,7 @@ class CaseInsensitiveDict(MutableMapping):
34
34
 
35
35
  def lower_items(self):
36
36
  """Like iteritems(), but with all lowercase keys."""
37
- return (
38
- (lowerkey, keyval[1])
39
- for (lowerkey, keyval)
40
- in self._store.items()
41
- )
37
+ return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items())
42
38
 
43
39
  def __eq__(self, other):
44
40
  if isinstance(other, Mapping):