Skip to content

Commit fc2cf5a

Browse files
committed
[IMP] Added DSA algorithm
1 parent 7021072 commit fc2cf5a

4 files changed

Lines changed: 155 additions & 1 deletion

File tree

src/xmlsig/algorithms/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
from .hmac import HMACAlgorithm
22
from .rsa import RSAAlgorithm
3+
from .dsa import DSAAlgorithm

src/xmlsig/algorithms/dsa.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# © 2017 Creu Blanca
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3+
4+
import struct
5+
from base64 import b64decode, b64encode
6+
7+
from asn1crypto.algos import DSASignature
8+
from cryptography.hazmat.backends import default_backend
9+
from cryptography.hazmat.primitives.asymmetric import dsa
10+
from xmlsig.algorithms.base import Algorithm
11+
from xmlsig.ns import NS_MAP, DSigNs
12+
from xmlsig.utils import USING_PYTHON2, b64_print, create_node, long_to_bytes
13+
14+
15+
def i2osp(x, x_len):
16+
if x >= pow(256, x_len):
17+
raise ValueError("integer too large")
18+
digits = []
19+
while x:
20+
digits.append(int(x % 256))
21+
x //= 256
22+
for _i in range(x_len - len(digits)):
23+
digits.append(0)
24+
return bytearray(digits[::-1])
25+
26+
27+
def os2ip(arr):
28+
x_len = len(arr)
29+
x = 0
30+
for i in range(x_len):
31+
if USING_PYTHON2:
32+
val = struct.unpack("B", arr[i])[0]
33+
else:
34+
val = arr[i]
35+
x = x + (val * pow(256, x_len - i - 1))
36+
return x
37+
38+
39+
def to_int(element):
40+
if USING_PYTHON2:
41+
return struct.unpack("B", element)[0]
42+
return element
43+
44+
45+
class DSAAlgorithm(Algorithm):
46+
private_key_class = dsa.DSAPrivateKey
47+
public_key_class = dsa.DSAPublicKey
48+
49+
@staticmethod
50+
def sign(data, private_key, digest):
51+
return DSASignature.load(private_key.sign(data, digest())).to_p1363()
52+
53+
@staticmethod
54+
def verify(signature_value, data, public_key, digest):
55+
public_key.verify(
56+
DSASignature.from_p1363(b64decode(signature_value)).dump(), data, digest()
57+
)
58+
59+
@staticmethod
60+
def key_value(node, public_key):
61+
result = create_node("DSAKeyValue", node, DSigNs, "\n", "\n")
62+
public_numbers = public_key.public_numbers()
63+
if public_numbers.parameter_numbers.p is not None:
64+
create_node(
65+
"P",
66+
result,
67+
DSigNs,
68+
tail="\n",
69+
text="\n"
70+
+ b64_print(
71+
b64encode(long_to_bytes(public_numbers.parameter_numbers.p))
72+
)
73+
+ "\n",
74+
)
75+
if public_numbers.parameter_numbers.q is not None:
76+
create_node(
77+
"Q",
78+
result,
79+
DSigNs,
80+
tail="\n",
81+
text="\n"
82+
+ b64_print(
83+
b64encode(long_to_bytes(public_numbers.parameter_numbers.q))
84+
)
85+
+ "\n",
86+
)
87+
if public_numbers.parameter_numbers.g is not None:
88+
create_node(
89+
"G",
90+
result,
91+
DSigNs,
92+
tail="\n",
93+
text="\n"
94+
+ b64_print(
95+
b64encode(long_to_bytes(public_numbers.parameter_numbers.g))
96+
)
97+
+ "\n",
98+
)
99+
if public_numbers.y is not None:
100+
create_node(
101+
"Y",
102+
result,
103+
DSigNs,
104+
tail="\n",
105+
text="\n"
106+
+ b64_print(b64encode(long_to_bytes(public_numbers.y)))
107+
+ "\n",
108+
)
109+
return result
110+
111+
@staticmethod
112+
def get_public_key(key_info, context):
113+
key = key_info.find("ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue", namespaces=NS_MAP)
114+
if key is not None:
115+
p = os2ip(b64decode(key.find("ds:P", namespaces=NS_MAP).text))
116+
q = os2ip(b64decode(key.find("ds:Q", namespaces=NS_MAP).text))
117+
g = os2ip(b64decode(key.find("ds:G", namespaces=NS_MAP).text))
118+
y = os2ip(b64decode(key.find("ds:Y", namespaces=NS_MAP).text))
119+
return dsa.DSAPublicNumbers(y, dsa.DSAParameterNumbers(p, q, g)).public_key(
120+
default_backend()
121+
)
122+
return super(DSAAlgorithm, DSAAlgorithm).get_public_key(key_info, context)

src/xmlsig/constants.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from cryptography.hazmat.primitives import hashes
55

6-
from .algorithms import HMACAlgorithm, RSAAlgorithm
6+
from .algorithms import DSAAlgorithm, HMACAlgorithm, RSAAlgorithm
77
from .ns import NS_MAP # noqa:F401
88
from .ns import DSignNsMore, DSigNs, DSigNs11, EncNs
99

@@ -102,6 +102,8 @@
102102
TransformHmacSha256: {"digest": hashes.SHA256, "method": HMACAlgorithm},
103103
TransformHmacSha384: {"digest": hashes.SHA384, "method": HMACAlgorithm},
104104
TransformHmacSha512: {"digest": hashes.SHA512, "method": HMACAlgorithm},
105+
TransformDsaSha1: {"digest": hashes.SHA1, "method": DSAAlgorithm},
106+
TransformDsaSha256: {"digest": hashes.SHA256, "method": DSAAlgorithm},
105107
}
106108

107109
TransformUsageEncryptionMethod = {}

tests/test_dsa.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import unittest
2+
from os import path
3+
4+
import xmlsig
5+
from cryptography.hazmat.primitives.serialization import pkcs12
6+
7+
from .base import BASE_DIR, parse_xml
8+
9+
10+
class TestDSASignature(unittest.TestCase):
11+
def test_dsa(self):
12+
root = parse_xml("data/sign-dsa-in.xml")
13+
sign = root.xpath("//ds:Signature", namespaces={"ds": xmlsig.constants.DSigNs})[
14+
0
15+
]
16+
self.assertIsNotNone(sign)
17+
ctx = xmlsig.SignatureContext()
18+
with open(path.join(BASE_DIR, "data/dsacred.p12"), "rb") as key_file:
19+
ctx.load_pkcs12(pkcs12.load_key_and_certificates(key_file.read(), None))
20+
ctx.sign(sign)
21+
ctx.verify(sign)
22+
23+
def test_verify(self):
24+
ctx = xmlsig.SignatureContext()
25+
root = parse_xml("data/sign-dsa-out.xml")
26+
sign = root.xpath("//ds:Signature", namespaces={"ds": xmlsig.constants.DSigNs})[
27+
0
28+
]
29+
ctx.verify(sign)

0 commit comments

Comments
 (0)