diff --git a/tests/clientDSACert.pem b/tests/clientDSACert.pem new file mode 100644 index 00000000..558d3405 --- /dev/null +++ b/tests/clientDSACert.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEYzCCBBGgAwIBAgIUeLiaH57flwaXt7G1ae3irblrBR0wCwYJYIZIAWUDBAMC +MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0yMzA4MDQxNjQ5MzhaGA8yMTIzMDcx +MTE2NDkzOFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIDQjCCAjUGByqGSM44BAEw +ggIoAoIBAQCKiKCtENzZfSCSfewPFVgV0vmOVw8WqO4uhiN/nSddlrxNOS58f5fk +whAmtKxH7mQqlFPADuscHy5NQl9GsKZPi5ogVtxImJwP7enyf056tcOCtCUWnMIQ +eEossgWdYE51KQeX+Hmr3XHqJwpsAW9yNkDS46SXut+uAAXLoQ2w+a71BJavCCuZ +P5CqOjMbKVDBMIGPwDUKaL1prq2VBVSZ8WTiAtqqgaQUZQuV88G5R46teyNdOHt8 +zonXF8/6PUuDbI3KXvQZitLo6dhXGrAg99Pwg9k9ztzUJOeZ7kU6DZFaawRT+/9J +f6iG0Le20jVc9aw9SbWNfJBY7szeaq11Ah0A/Rgp6nI7FlqEUqL9GaNBm+2B/E1D +P3Z5n3prNQKCAQBV7L0KOF5OnXYMNUrVSZ7qP9hMP4dnhVPvqVa0LJhXbmYx8CO+ +kdPGbX2t8skg5yM1vK+AZ/nM5tlbA2R/F6Nol3X9lwUwQT0RRlhsHoT7kTVMU+Ip +lv1C3X+H3GthGAnMmGutGsK+48sHl6QAPmy8f6uVHWzh6WQe7pROZPD410U8t2GM +wRUjlo8n5NdQx/Qw7bvT88HX3FpybNQQJvsefeX5gEpK8JQSLhOEEjAqafrJHtBM +FBDsdpn5/5/H9gTBXqjq6zJRxQP+xggmLj2Eo9B249ANmi57pixM2m9NFXVneJJN +zlIZ2DIEzOX8TVjDFyGLmldXIz2/BqG6dmYDA4IBBQACggEAQDA5L5QJubxun5sH +w31gN/oXWyxF9qXoaUyLpUpt2V2lebNCJ6bdR+wstA7ddyAb/pM6kjnjEv+UxD3c +SjzMtW+Eehn8kXv4gdYyJeJjFcziV5sFvjW6RGAQX0+YO1gAv8BxjGzySNoc7bpp +Ta5YywN0sRPG3U0KsVGyBXakYNoS3Hq7n1JnrSuGBKVje1fQyesRtpkWBfD6Dlog +iciOy/iQnTsMCFrlrETJPfOAJ12Gzsr+lL9TLVsIP9tAk++Arp1H/rICf2QpIjsZ +uEehHgg3oA62RecmMBf+7HX2+bZeoRyYYCe6DMyDS7HLzKA/y31iw7E9VLNyKU4q +i6VPLqNTMFEwHQYDVR0OBBYEFBFcfDeT5cNGJwP89U5VkvxojDuzMB8GA1UdIwQY +MBaAFBFcfDeT5cNGJwP89U5VkvxojDuzMA8GA1UdEwEB/wQFMAMBAf8wCwYJYIZI +AWUDBAMCAz8AMDwCHGaJRUSP9RhmtFPC7UZQ3KjyELgZ/vUcfoJa3ysCHEUavN6C +jExdFwdKI/98/jX7Qz7IlKnDbww8K9c= +-----END CERTIFICATE----- diff --git a/tests/clientDSAKey.pem b/tests/clientDSAKey.pem new file mode 100644 index 00000000..0f8fdf44 --- /dev/null +++ b/tests/clientDSAKey.pem @@ -0,0 +1,15 @@ +-----BEGIN PRIVATE KEY----- +MIICXAIBADCCAjUGByqGSM44BAEwggIoAoIBAQCKiKCtENzZfSCSfewPFVgV0vmO +Vw8WqO4uhiN/nSddlrxNOS58f5fkwhAmtKxH7mQqlFPADuscHy5NQl9GsKZPi5og +VtxImJwP7enyf056tcOCtCUWnMIQeEossgWdYE51KQeX+Hmr3XHqJwpsAW9yNkDS +46SXut+uAAXLoQ2w+a71BJavCCuZP5CqOjMbKVDBMIGPwDUKaL1prq2VBVSZ8WTi +AtqqgaQUZQuV88G5R46teyNdOHt8zonXF8/6PUuDbI3KXvQZitLo6dhXGrAg99Pw +g9k9ztzUJOeZ7kU6DZFaawRT+/9Jf6iG0Le20jVc9aw9SbWNfJBY7szeaq11Ah0A +/Rgp6nI7FlqEUqL9GaNBm+2B/E1DP3Z5n3prNQKCAQBV7L0KOF5OnXYMNUrVSZ7q +P9hMP4dnhVPvqVa0LJhXbmYx8CO+kdPGbX2t8skg5yM1vK+AZ/nM5tlbA2R/F6No +l3X9lwUwQT0RRlhsHoT7kTVMU+Iplv1C3X+H3GthGAnMmGutGsK+48sHl6QAPmy8 +f6uVHWzh6WQe7pROZPD410U8t2GMwRUjlo8n5NdQx/Qw7bvT88HX3FpybNQQJvse +feX5gEpK8JQSLhOEEjAqafrJHtBMFBDsdpn5/5/H9gTBXqjq6zJRxQP+xggmLj2E +o9B249ANmi57pixM2m9NFXVneJJNzlIZ2DIEzOX8TVjDFyGLmldXIz2/BqG6dmYD +BB4CHCEci705ssiT3yVp22s8gZEDANgtPXZaMjAFEMw= +-----END PRIVATE KEY----- diff --git a/tests/tlstest.py b/tests/tlstest.py index 5ed3e740..18a64b73 100755 --- a/tests/tlstest.py +++ b/tests/tlstest.py @@ -999,6 +999,29 @@ def connect(): test_no += 1 + print("Test {0} - good mutual X.509 DSA, TLSv1.2".format(test_no)) + with open(os.path.join(dir, "clientDSACert.pem")) as f: + x509DSACert = X509().parse(f.read()) + x509DSAChain = X509CertChain([x509DSACert]) + with open(os.path.join(dir, "clientDSAKey.pem")) as f: + x509DSAKey = parsePEMKey(f.read(), private=True) + + synchro.recv(1) + connection = connect() + settings = HandshakeSettings() + settings.minVersion = (3, 3) + settings.maxVersion = (3, 3) + connection.handshakeClientCert(x509DSAChain, x509DSAKey, settings=settings) + testConnClient(connection) + assert connection.session.cipherSuite in\ + constants.CipherSuite.dheDsaSuites + assert isinstance(connection.session.serverCertChain, X509CertChain) + assert connection.session.serverCertChain.getEndEntityPublicKey().key_type\ + == "dsa" + connection.close() + + test_no += 1 + print("Test {0} - good X.509 Ed25519, TLSv1.2".format(test_no)) synchro.recv(1) connection = connect() @@ -2683,6 +2706,22 @@ def connect(): test_no += 1 + print("Test {0} - good mutual X.509 DSA, TLSv1.2".format(test_no)) + synchro.send(b'R') + connection = connect() + settings = HandshakeSettings() + settings.minVersion = (3, 3) + settings.maxVersion = (3, 3) + connection.handshakeServer(certChain=x509ChainDSA, reqCert=True, + privateKey=x509KeyDSA, settings=settings) + assert(isinstance(connection.session.clientCertChain, X509CertChain)) + assert connection.session.clientCertChain.getEndEntityPublicKey().key_type\ + == "dsa" + testConnServer(connection) + connection.close() + + test_no += 1 + print("Test {0} - good X.509 Ed25519, TLSv1.2".format(test_no)) synchro.send(b'R') connection = connect() diff --git a/tlslite/constants.py b/tlslite/constants.py index 3f95e72d..dd958c57 100644 --- a/tlslite/constants.py +++ b/tlslite/constants.py @@ -1515,13 +1515,13 @@ def getEcdsaSuites(cls, settings, version=None): #: DHE key exchange, DSA authentication dheDsaSuites = [] - dheDsaSuites.append(TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA) - dheDsaSuites.append(TLS_DHE_DSS_WITH_AES_128_CBC_SHA) - dheDsaSuites.append(TLS_DHE_DSS_WITH_AES_256_CBC_SHA) - dheDsaSuites.append(TLS_DHE_DSS_WITH_AES_128_CBC_SHA256) - dheDsaSuites.append(TLS_DHE_DSS_WITH_AES_256_CBC_SHA256) - dheDsaSuites.append(TLS_DHE_DSS_WITH_AES_128_GCM_SHA256) dheDsaSuites.append(TLS_DHE_DSS_WITH_AES_256_GCM_SHA384) + dheDsaSuites.append(TLS_DHE_DSS_WITH_AES_128_GCM_SHA256) + dheDsaSuites.append(TLS_DHE_DSS_WITH_AES_256_CBC_SHA256) + dheDsaSuites.append(TLS_DHE_DSS_WITH_AES_128_CBC_SHA256) + dheDsaSuites.append(TLS_DHE_DSS_WITH_AES_256_CBC_SHA) + dheDsaSuites.append(TLS_DHE_DSS_WITH_AES_128_CBC_SHA) + dheDsaSuites.append(TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA) @classmethod def getDheDsaSuites(cls, settings, version=None): diff --git a/tlslite/keyexchange.py b/tlslite/keyexchange.py index d8c32f46..bea1644a 100644 --- a/tlslite/keyexchange.py +++ b/tlslite/keyexchange.py @@ -373,6 +373,9 @@ def calcVerifyBytes(version, handshakeHashes, signatureAlg, SignatureScheme.ed448): hashName = "intrinsic" padding = None + elif signatureAlg[1] == SignatureAlgorithm.dsa: + hashName = HashAlgorithm.toRepr(signatureAlg[0]) + padding = None elif signatureAlg[1] != SignatureAlgorithm.ecdsa: scheme = SignatureScheme.toRepr(signatureAlg) if scheme is None: @@ -455,6 +458,13 @@ def makeCertificateVerify(version, handshakeHashes, validSigAlgs, verifyBytes = verifyBytes[:privateKey.private_key.curve.baselen] sig_func = privateKey.sign ver_func = privateKey.verify + elif signatureAlgorithm and \ + signatureAlgorithm[1] == SignatureAlgorithm.dsa: + padding = None + hashName = HashAlgorithm.toRepr(signatureAlgorithm[0]) + saltLen = None + sig_func = privateKey.sign + ver_func = privateKey.verify else: scheme = SignatureScheme.toRepr(signatureAlgorithm) # for pkcs1 signatures hash is used to add PKCS#1 prefix, but diff --git a/tlslite/tlsconnection.py b/tlslite/tlsconnection.py index bd5cf82c..582097a7 100644 --- a/tlslite/tlsconnection.py +++ b/tlslite/tlsconnection.py @@ -1700,7 +1700,7 @@ def _clientKeyExchange(self, settings, cipherSuite, #abort if Certificate Request with inappropriate ciphersuite if cipherSuite not in CipherSuite.certAllSuites \ and cipherSuite not in CipherSuite.ecdheEcdsaSuites \ - and CipherSuite not in CipherSuite.dheDsaSuites\ + and cipherSuite not in CipherSuite.dheDsaSuites\ or cipherSuite in CipherSuite.srpAllSuites: for result in self._sendError(\ AlertDescription.unexpected_message, @@ -1767,7 +1767,6 @@ def _clientKeyExchange(self, settings, cipherSuite, #Send Certificate if we were asked for it if certificateRequest: - # if a peer doesn't advertise support for any algorithm in TLSv1.2, # support for SHA1+RSA can be assumed if self.version == (3, 3)\ @@ -2826,7 +2825,7 @@ def _serverTLS13Handshake(self, settings, clientHello, cipherSuite, ctx = b'' # Get list of valid Signing Algorithms - # we don't support DSA for client certificates yet + # DSA is not supported for TLS 1.3 cr_settings = settings.validate() cr_settings.dsaSigHashes = [] valid_sig_algs = self._sigHashesToList(cr_settings) @@ -4206,11 +4205,17 @@ def _serverCertKeyExchange(self, clientHello, serverHello, sigHashAlg, if not reqCAs: reqCAs = [] cr_settings = settings.validate() - # we don't support DSA in client certificates yet - cr_settings.dsaSigHashes = [] valid_sig_algs = self._sigHashesToList(cr_settings) - certificateRequest.create([ClientCertificateType.rsa_sign, - ClientCertificateType.ecdsa_sign], + + cert_types = [] + if cr_settings.rsaSigHashes: + cert_types.append(ClientCertificateType.rsa_sign) + if cr_settings.ecdsaSigHashes or cr_settings.more_sig_schemes: + cert_types.append(ClientCertificateType.ecdsa_sign) + if cr_settings.dsaSigHashes: + cert_types.append(ClientCertificateType.dss_sign) + + certificateRequest.create(cert_types, reqCAs, valid_sig_algs) msgs.append(certificateRequest) @@ -4327,6 +4332,12 @@ def _serverCertKeyExchange(self, clientHello, serverHello, sigHashAlg, salt_len = None padding = None ver_func = public_key.hashAndVerify + elif signatureAlgorithm and \ + signatureAlgorithm[1] == SignatureAlgorithm.dsa: + padding = None + hash_name = HashAlgorithm.toRepr(signatureAlgorithm[0]) + salt_len = None + ver_func = public_key.verify elif not signatureAlgorithm or \ signatureAlgorithm[1] != SignatureAlgorithm.ecdsa: scheme = SignatureScheme.toRepr(signatureAlgorithm) diff --git a/tlslite/utils/python_dsakey.py b/tlslite/utils/python_dsakey.py index 30a47c63..b590881e 100644 --- a/tlslite/utils/python_dsakey.py +++ b/tlslite/utils/python_dsakey.py @@ -33,11 +33,10 @@ def __init__(self, p=0, q=0, g=0, x=0, y=0): self.g = g self.private_key = x self.public_key = y + if self.private_key and not self.public_key: + self.public_key = powMod(g, self.private_key, p) self.key_type = "dsa" - if p and q and p < q: - raise ValueError("q is greater than p") - def __len__(self): return numBits(self.p) @@ -82,7 +81,21 @@ def hashAndSign(self, data, hAlg="sha1"): hashData = (secureHash(bytearray(data), hAlg)) return self.sign(hashData) - def sign(self, data): + def sign(self, data, padding=None, hashAlg=None, saltLen=None): + """ + :type data: bytearray + :param data: The value which will be signed (generally a binary + encoding of hash output. + + :type padding: str + :param padding: Ignored, present for API compatibility with RSA + + :type hashAlg: str + :param hashAlg: name of hash that was used for calculating the bytes + + :type saltLen: int + :param saltLen: Ignored, present for API compatibility with RSA + """ N = numBits(self.q) digest_len = len(data) * 8 digest = bytesToNumber(data) @@ -90,12 +103,38 @@ def sign(self, data): digest >>= digest_len - N k = getRandomNumber(1, (self.q-1)) + if gmpyLoaded or GMPY2_LOADED: + k = mpz(k) + digest = mpz(digest) r = powMod(self.g, k, self.p) % self.q s = invMod(k, self.q) * (digest + self.private_key * r) % self.q return encode_sequence(encode_integer(r), encode_integer(s)) - def verify(self, signature, hashData): + def verify(self, signature, hashData, padding=None, hashAlg=None, + saltLen=None): + """Verify the passed-in bytes with the signature. + + This verifies a DSA signature on the passed-in data. + + :type signature: bytearray + :param signature: The signature. + + :type hashData: bytearray + :param hashData: The value which will be verified. + + :type padding: str + :param padding: Ignored, present for API compatibility with RSA + + :type hashAlg: str + :param hashAlg: Ignored, present for API compatibility with RSA + + :type saltLen: str + :param saltLen: Ignored, present for API compatibility with RSA + + :rtype: bool + :returns: Whether the signature matches the passed-in data. + """ N = numBits(self.q) digest_len = len(hashData) * 8 digest = bytesToNumber(hashData) @@ -125,8 +164,8 @@ def verify(self, signature, hashData): w = invMod(s, self.q) u1 = (digest * w) % self.q u2 = (r * w) % self.q - v = ((powMod(self.g, u1, self.p) * \ - powMod(self.public_key, u2, self.p)) % self.p) % self.q + v = ((powMod(self.g, u1, self.p) * \ + powMod(self.public_key, u2, self.p)) % self.p) % self.q return r == v return False