File: pytlsbinding.py

package info (click to toggle)
firefox 148.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,719,656 kB
  • sloc: cpp: 7,618,171; javascript: 6,701,506; ansic: 3,781,787; python: 1,418,364; xml: 638,647; asm: 438,962; java: 186,285; sh: 62,885; makefile: 19,010; objc: 13,092; perl: 12,763; yacc: 4,583; cs: 3,846; pascal: 3,448; lex: 1,720; ruby: 1,003; php: 436; lisp: 258; awk: 247; sql: 66; sed: 54; csh: 10; exp: 6
file content (137 lines) | stat: -rw-r--r-- 5,141 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
#!/usr/bin/env python
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""
Helper library for creating a 2-QWACs TLS certificate binding given the details
of a signing certificate and a certificate to bind. See ETSI TS 119 411-5
V2.1.1 Annex B.

When run with an output file-like object and a path to a file containing
a specification, creates a TLS certificate binding from the given information
and writes it to the output object. The specification is as follows:

signingCertificate:
<certificate specification>
:end
certificateToBind:
<certificate specification>
:end

Where:
  <> indicates a required component of a field
  ":end" indicates the end of a multi-line specification

Currently only the algorithms RS256 (RSA PKCS#1v1.5 with SHA-256) and S256
(SHA-256) are supported.
"""

import base64
import hashlib
import json
from io import StringIO

import pycert
import pykey


def urlsafebase64(b):
    """Helper function that takes a bytes-like object and returns the
    urlsafebase64-encoded bytes without any trailing '='."""
    return base64.urlsafe_b64encode(b).decode().replace("=", "").encode("utf-8")


class Header:
    """Class representing a 2-QWACs TLS certificate binding header."""

    def __init__(self, signingCertificate, certificateToBind):
        self.signingCertificate = signingCertificate
        self.certificateToBind = certificateToBind

    def __str__(self):
        signingCertificateBase64 = base64.standard_b64encode(
            self.signingCertificate.toDER()
        ).decode()
        certificateToBindDER = self.certificateToBind.toDER()
        certificateToBindBase64Urlsafe = urlsafebase64(certificateToBindDER)
        certificateToBindHash = urlsafebase64(
            hashlib.sha256(certificateToBindBase64Urlsafe).digest()
        ).decode()
        header = {
            "alg": "RS256",
            "cty": "TLS-Certificate-Binding-v1",
            "x5c": [signingCertificateBase64],
            "sigD": {
                "mId": "http://uri.etsi.org/19182/ObjectIdByURIHash",
                "pars": [""],
                "hashM": "S256",
                "hashV": [certificateToBindHash],
            },
        }
        return json.dumps(header)


class TLSBinding:
    """Class representing a 2-QWACs TLS certificate binding."""

    def __init__(self, signingCertificate, certificateToBind):
        self.signingCertificate = signingCertificate
        self.certificateToBind = certificateToBind

    @staticmethod
    def fromSpecification(specStream):
        """Constructs a TLS certificate binding from a specification."""
        signingCertificateSpecification = StringIO()
        readingSigningCertificateSpecification = False
        certificateToBindSpecification = StringIO()
        readingCertificateToBindSpecification = False
        for line in specStream.readlines():
            lineStripped = line.strip()
            if readingSigningCertificateSpecification:
                if lineStripped == ":end":
                    readingSigningCertificateSpecification = False
                else:
                    print(lineStripped, file=signingCertificateSpecification)
            elif readingCertificateToBindSpecification:
                if lineStripped == ":end":
                    readingCertificateToBindSpecification = False
                else:
                    print(lineStripped, file=certificateToBindSpecification)
            elif lineStripped == "certificateToBind:":
                readingCertificateToBindSpecification = True
            elif lineStripped == "signingCertificate:":
                readingSigningCertificateSpecification = True
            else:
                raise pycert.UnknownParameterTypeError(lineStripped)
        signingCertificateSpecification.seek(0)
        signingCertificate = pycert.Certificate(signingCertificateSpecification)
        certificateToBindSpecification.seek(0)
        certificateToBind = pycert.Certificate(certificateToBindSpecification)
        return TLSBinding(signingCertificate, certificateToBind)

    def signAndEncode(self):
        """Returns a signed and encoded representation of the TLS certificate
        binding as bytes."""
        header = urlsafebase64(
            str(Header(self.signingCertificate, self.certificateToBind)).encode("utf-8")
        )
        signature = self.signingCertificate.subjectKey.sign(
            header + b".", pykey.HASH_SHA256
        )
        # signature will be of the form "'AABBCC...'H"
        return (
            header.decode()
            + ".."
            + urlsafebase64(bytes.fromhex(signature[1:-2])).decode()
        )


# The build harness will call this function with an output
# file-like object and a path to a file containing an SCT
# specification. This will read the specification and output
# the SCT as bytes.
def main(output, inputPath):
    with open(inputPath) as configStream:
        output.write(TLSBinding.fromSpecification(configStream).signAndEncode())