1# Copyright 2015 Google Inc. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""OpenSSL Crypto-related routines for oauth2client."""
15
16from OpenSSL import crypto
17
18from oauth2client import _helpers
19
20
21class OpenSSLVerifier(object):
22    """Verifies the signature on a message."""
23
24    def __init__(self, pubkey):
25        """Constructor.
26
27        Args:
28            pubkey: OpenSSL.crypto.PKey, The public key to verify with.
29        """
30        self._pubkey = pubkey
31
32    def verify(self, message, signature):
33        """Verifies a message against a signature.
34
35        Args:
36        message: string or bytes, The message to verify. If string, will be
37                 encoded to bytes as utf-8.
38        signature: string or bytes, The signature on the message. If string,
39                   will be encoded to bytes as utf-8.
40
41        Returns:
42            True if message was signed by the private key associated with the
43            public key that this object was constructed with.
44        """
45        message = _helpers._to_bytes(message, encoding='utf-8')
46        signature = _helpers._to_bytes(signature, encoding='utf-8')
47        try:
48            crypto.verify(self._pubkey, signature, message, 'sha256')
49            return True
50        except crypto.Error:
51            return False
52
53    @staticmethod
54    def from_string(key_pem, is_x509_cert):
55        """Construct a Verified instance from a string.
56
57        Args:
58            key_pem: string, public key in PEM format.
59            is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it
60                          is expected to be an RSA key in PEM format.
61
62        Returns:
63            Verifier instance.
64
65        Raises:
66            OpenSSL.crypto.Error: if the key_pem can't be parsed.
67        """
68        key_pem = _helpers._to_bytes(key_pem)
69        if is_x509_cert:
70            pubkey = crypto.load_certificate(crypto.FILETYPE_PEM, key_pem)
71        else:
72            pubkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key_pem)
73        return OpenSSLVerifier(pubkey)
74
75
76class OpenSSLSigner(object):
77    """Signs messages with a private key."""
78
79    def __init__(self, pkey):
80        """Constructor.
81
82        Args:
83            pkey: OpenSSL.crypto.PKey (or equiv), The private key to sign with.
84        """
85        self._key = pkey
86
87    def sign(self, message):
88        """Signs a message.
89
90        Args:
91            message: bytes, Message to be signed.
92
93        Returns:
94            string, The signature of the message for the given key.
95        """
96        message = _helpers._to_bytes(message, encoding='utf-8')
97        return crypto.sign(self._key, message, 'sha256')
98
99    @staticmethod
100    def from_string(key, password=b'notasecret'):
101        """Construct a Signer instance from a string.
102
103        Args:
104            key: string, private key in PKCS12 or PEM format.
105            password: string, password for the private key file.
106
107        Returns:
108            Signer instance.
109
110        Raises:
111            OpenSSL.crypto.Error if the key can't be parsed.
112        """
113        key = _helpers._to_bytes(key)
114        parsed_pem_key = _helpers._parse_pem_key(key)
115        if parsed_pem_key:
116            pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key)
117        else:
118            password = _helpers._to_bytes(password, encoding='utf-8')
119            pkey = crypto.load_pkcs12(key, password).get_privatekey()
120        return OpenSSLSigner(pkey)
121
122
123def pkcs12_key_as_pem(private_key_bytes, private_key_password):
124    """Convert the contents of a PKCS#12 key to PEM using pyOpenSSL.
125
126    Args:
127        private_key_bytes: Bytes. PKCS#12 key in DER format.
128        private_key_password: String. Password for PKCS#12 key.
129
130    Returns:
131        String. PEM contents of ``private_key_bytes``.
132    """
133    private_key_password = _helpers._to_bytes(private_key_password)
134    pkcs12 = crypto.load_pkcs12(private_key_bytes, private_key_password)
135    return crypto.dump_privatekey(crypto.FILETYPE_PEM,
136                                  pkcs12.get_privatekey())
137