Skip to content

Commit 25bbf67

Browse files
Merge pull request #526 from c00kiemon5ter/refactor-cryptography-module
Introduce cryptography module
2 parents 79d6798 + a7763cd commit 25bbf67

File tree

9 files changed

+266
-139
lines changed

9 files changed

+266
-139
lines changed

src/saml2/aes.py

Lines changed: 12 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,18 @@
1-
import os
2-
from base64 import b64decode
3-
from base64 import b64encode
1+
import warnings as _warnings
42

5-
from cryptography.hazmat.backends import default_backend
6-
from cryptography.hazmat.primitives.ciphers import Cipher
7-
from cryptography.hazmat.primitives.ciphers import algorithms
8-
from cryptography.hazmat.primitives.ciphers import modes
3+
from saml2.cryptography.symmetric import AESCipher as _AESCipher
94

105

11-
POSTFIX_MODE = {
12-
'cbc': modes.CBC,
13-
'cfb': modes.CFB,
14-
}
6+
_deprecation_msg = (
7+
'{name} {type} is deprecated. '
8+
'It will be removed in the next version. '
9+
'Use saml2.cryptography.symmetric instead.'
10+
).format(name=__name__, type='module')
1511

16-
AES_BLOCK_SIZE = int(algorithms.AES.block_size / 8)
12+
_warnings.simplefilter('default')
13+
_warnings.warn(_deprecation_msg, DeprecationWarning)
1714

1815

19-
class AESCipher(object):
20-
def __init__(self, key):
21-
"""
22-
:param key: The encryption key
23-
:return: AESCipher instance
24-
"""
25-
self.key = key
26-
27-
def build_cipher(self, alg='aes_128_cbc'):
28-
"""
29-
:param alg: cipher algorithm
30-
:return: A Cipher instance
31-
"""
32-
typ, bits, cmode = alg.lower().split('_')
33-
bits = int(bits)
34-
iv = os.urandom(AES_BLOCK_SIZE)
35-
36-
if len(iv) != AES_BLOCK_SIZE:
37-
raise Exception('Wrong iv size: {}'.format(len(iv)))
38-
39-
if bits not in algorithms.AES.key_sizes:
40-
raise Exception('Unsupported key length: {}'.format(bits))
41-
42-
if len(self.key) != bits / 8:
43-
raise Exception('Wrong Key length: {}'.format(len(self.key)))
44-
45-
try:
46-
mode = POSTFIX_MODE[cmode]
47-
except KeyError:
48-
raise Exception('Unsupported chaining mode: {}'.format(cmode))
49-
50-
cipher = Cipher(
51-
algorithms.AES(self.key),
52-
mode(iv),
53-
backend=default_backend())
54-
55-
return cipher, iv
56-
57-
def encrypt(self, msg, alg='aes_128_cbc', padding='PKCS#7', b64enc=True,
58-
block_size=AES_BLOCK_SIZE):
59-
"""
60-
:param key: The encryption key
61-
:param msg: Message to be encrypted
62-
:param padding: Which padding that should be used
63-
:param b64enc: Whether the result should be base64encoded
64-
:param block_size: If PKCS#7 padding which block size to use
65-
:return: The encrypted message
66-
"""
67-
68-
if padding == 'PKCS#7':
69-
_block_size = block_size
70-
elif padding == 'PKCS#5':
71-
_block_size = 8
72-
else:
73-
_block_size = 0
74-
75-
if _block_size:
76-
plen = _block_size - (len(msg) % _block_size)
77-
c = chr(plen).encode()
78-
msg += c * plen
79-
80-
cipher, iv = self.build_cipher(alg)
81-
encryptor = cipher.encryptor()
82-
cmsg = iv + encryptor.update(msg) + encryptor.finalize()
83-
84-
if b64enc:
85-
enc_msg = b64encode(cmsg)
86-
else:
87-
enc_msg = cmsg
88-
89-
return enc_msg
90-
91-
def decrypt(self, msg, alg='aes_128_cbc', padding='PKCS#7', b64dec=True):
92-
"""
93-
:param key: The encryption key
94-
:param msg: Base64 encoded message to be decrypted
95-
:return: The decrypted message
96-
"""
97-
data = b64decode(msg) if b64dec else msg
98-
99-
cipher, iv = self.build_cipher(alg=alg)
100-
decryptor = cipher.decryptor()
101-
res = decryptor.update(data)[AES_BLOCK_SIZE:] + decryptor.finalize()
102-
if padding in ['PKCS#5', 'PKCS#7']:
103-
idx = bytearray(res)[-1]
104-
res = res[:-idx]
105-
return res
16+
AESCipher = _AESCipher
17+
POSTFIX_MODE = _AESCipher.POSTFIX_MODE
18+
AES_BLOCK_SIZE = _AESCipher.AES_BLOCK_SIZE

src/saml2/authn.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import warnings
12
import logging
23
import six
34
import time
45
from saml2 import SAMLError
5-
from saml2.aes import AESCipher
6+
import saml2.cryptography.symmetric
67
from saml2.httputil import Response
78
from saml2.httputil import make_cookie
89
from saml2.httputil import Redirect
@@ -14,6 +15,7 @@
1415
__author__ = 'rolandh'
1516

1617
logger = logging.getLogger(__name__)
18+
warnings.simplefilter('default')
1719

1820

1921
class AuthnFailure(SAMLError):
@@ -120,7 +122,16 @@ def __init__(self, srv, mako_template, template_lookup, pwd, return_to):
120122
self.return_to = return_to
121123
self.active = {}
122124
self.query_param = "upm_answer"
123-
self.aes = AESCipher(self.srv.symkey.encode())
125+
self.symmetric = saml2.cryptography.symmetric.Default(srv.symkey)
126+
127+
@property
128+
def aes(self):
129+
_deprecation_msg = (
130+
'This attribute is deprecated. '
131+
'It will be removed in the next version. '
132+
'Use self.symmetric instead.')
133+
warnings.warn(_deprecation_msg, DeprecationWarning)
134+
return self.symmetric
124135

125136
def __call__(self, cookie=None, policy_url=None, logo_url=None,
126137
query="", **kwargs):
@@ -172,7 +183,7 @@ def verify(self, request, **kwargs):
172183
self._verify(_dict["password"][0], _dict["login"][0])
173184
timestamp = str(int(time.mktime(time.gmtime())))
174185
msg = "::".join([_dict["login"][0], timestamp])
175-
info = self.aes.encrypt(msg.encode())
186+
info = self.symmetric.encrypt(msg.encode())
176187
self.active[info] = timestamp
177188
cookie = make_cookie(self.cookie_name, info, self.srv.seed)
178189
return_to = create_return_url(self.return_to, _dict["query"][0],
@@ -192,7 +203,7 @@ def authenticated_as(self, cookie=None, **kwargs):
192203
info, timestamp = parse_cookie(self.cookie_name,
193204
self.srv.seed, cookie)
194205
if self.active[info] == timestamp:
195-
msg = self.aes.decrypt(info).decode()
206+
msg = self.symmetric.decrypt(info).decode()
196207
uid, _ts = msg.split("::")
197208
if timestamp == _ts:
198209
return {"uid": uid}

src/saml2/cert.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@
99
from os.path import join
1010
from os import remove
1111

12-
from cryptography.hazmat.backends import default_backend
13-
from cryptography.x509 import load_pem_x509_certificate
12+
import saml2.cryptography.pki
1413

15-
backend = default_backend()
1614

1715
class WrongInput(Exception):
1816
pass
@@ -325,7 +323,8 @@ def verify(self, signing_cert_str, cert_str):
325323
cert_algorithm = cert_algorithm.decode('ascii')
326324
cert_str = cert_str.encode('ascii')
327325

328-
cert_crypto = load_pem_x509_certificate(cert_str, backend)
326+
cert_crypto = saml2.cryptography.pki.load_pem_x509_certificate(
327+
cert_str)
329328

330329
try:
331330
crypto.verify(ca_cert, cert_crypto.signature,

src/saml2/cryptography/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""This module provides cryptographic elements needed by saml2."""

src/saml2/cryptography/asymmetric.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""This module provides methods for asymmetric cryptography."""
2+
3+
import cryptography.hazmat.backends as _backends
4+
import cryptography.hazmat.primitives.asymmetric as _asymmetric
5+
import cryptography.hazmat.primitives.hashes as _hashes
6+
import cryptography.hazmat.primitives.serialization as _serialization
7+
8+
9+
def load_pem_private_key(data, password):
10+
"""Load RSA PEM certificate."""
11+
key = _serialization.load_pem_private_key(
12+
data, password, _backends.default_backend())
13+
return key
14+
15+
16+
def key_sign(rsakey, message, digest):
17+
"""Sign the given message with the RSA key."""
18+
padding = _asymmetric.padding.PKCS1v15()
19+
signature = rsakey.sign(message, padding, digest)
20+
return signature
21+
22+
23+
def key_verify(rsakey, signature, message, digest):
24+
"""Verify the given signature with the RSA key."""
25+
padding = _asymmetric.padding.PKCS1v15()
26+
if isinstance(rsakey, _asymmetric.rsa.RSAPrivateKey):
27+
rsakey = rsakey.public_key()
28+
29+
try:
30+
rsakey.verify(signature, message, padding, digest)
31+
except Exception as e:
32+
return False
33+
else:
34+
return True
35+
36+
37+
hashes = _hashes

src/saml2/cryptography/pki.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""This module provides methods for PKI operations."""
2+
3+
import cryptography.hazmat.backends as _backends
4+
import cryptography.x509 as _x509
5+
6+
7+
def load_pem_x509_certificate(data):
8+
"""Load X.509 PEM certificate."""
9+
return _x509.load_pem_x509_certificate(data, _backends.default_backend())

0 commit comments

Comments
 (0)