File: sign.py

package info (click to toggle)
python-signedjson 1.1.4-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 192 kB
  • sloc: python: 375; makefile: 3
file content (141 lines) | stat: -rw-r--r-- 4,452 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# -*- coding: utf-8 -*-

# Copyright 2014 OpenMarket Ltd
# Copyright 2020 The Matrix.org Foundation C.I.C
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import logging
from typing import Any, Dict, Iterable, List

from canonicaljson import encode_canonical_json
from unpaddedbase64 import decode_base64, encode_base64

from signedjson.key import SUPPORTED_ALGORITHMS
from signedjson.types import SigningKey, VerifyKey

logger = logging.getLogger(__name__)

JsonDict = Dict[str, Any]


def sign_json(json_object, signature_name, signing_key):
    # type: (JsonDict, str, SigningKey) -> JsonDict
    """Sign the JSON object. Stores the signature in json_object["signatures"].

    Args:
        json_object (dict): The JSON object to sign.
        signature_name (str): The name of the signing entity.
        signing_key (syutil.crypto.SigningKey): The key to sign the JSON with.

    Returns:
        The modified, signed JSON object."""

    signatures = json_object.pop("signatures", {})
    unsigned = json_object.pop("unsigned", None)

    message_bytes = encode_canonical_json(json_object)
    signed = signing_key.sign(message_bytes)
    signature_base64 = encode_base64(signed.signature)

    key_id = "%s:%s" % (signing_key.alg, signing_key.version)
    signatures.setdefault(signature_name, {})[key_id] = signature_base64

    # logger.debug("SIGNING: %s %s %s", signature_name, key_id, message_bytes)

    json_object["signatures"] = signatures
    if unsigned is not None:
        json_object["unsigned"] = unsigned

    return json_object


def signature_ids(
    json_object, signature_name, supported_algorithms=SUPPORTED_ALGORITHMS
):
    # type: (JsonDict, str, Iterable[str]) -> List[str]
    """Does the JSON object have a signature for the given name?
    Args:
        json_object (dict): The JSON object to check.
        signature_name (str): The name of the signing entity to check for
        supported_algorithms (list of str): List of supported signature
            algorithms
    Returns:
        list of key identifier strings.
    """
    key_ids = json_object.get("signatures", {}).get(signature_name, {}).keys()
    return list(
        key_id for key_id in key_ids if key_id.split(":")[0] in supported_algorithms
    )


class SignatureVerifyException(Exception):
    """A signature could not be verified"""

    pass


def verify_signed_json(json_object, signature_name, verify_key):
    # type: (JsonDict, str, VerifyKey) -> None
    """Check a signature on a signed JSON object.

    Args:
        json_object: The signed JSON object to check.
        signature_name: The name of the signature to check.
        verify_key: The key to verify the signature.

    Raises:
        SignatureVerifyException: If the signature isn't valid
    """

    try:
        signatures = json_object["signatures"]
    except KeyError:
        raise SignatureVerifyException("No signatures on this object")

    key_id = "%s:%s" % (verify_key.alg, verify_key.version)

    try:
        signature_b64 = signatures[signature_name][key_id]
    except KeyError:
        raise SignatureVerifyException(
            "Missing signature for %s, %s" % (signature_name, key_id)
        )

    try:
        signature = decode_base64(signature_b64)
    except Exception:
        raise SignatureVerifyException(
            "Invalid signature base64 for %s, %s" % (signature_name, key_id)
        )

    json_object_copy = dict(json_object)
    del json_object_copy["signatures"]
    json_object_copy.pop("unsigned", None)

    message = encode_canonical_json(json_object_copy)

    # logger.debug("VERIFY: %s %s %s", signature_name, key_id, message)

    try:
        verify_key.verify(message, signature)
    except Exception as e:
        raise SignatureVerifyException(
            "Unable to verify signature for %s: %s %s"
            % (
                signature_name,
                type(e),
                e,
            )
        )