diff --git a/src/wolfcrypt/_build_ffi.py b/src/wolfcrypt/_build_ffi.py index e270ef4..728373c 100644 --- a/src/wolfcrypt/_build_ffi.py +++ b/src/wolfcrypt/_build_ffi.py @@ -69,6 +69,7 @@ PWDBASED_ENABLED = 0 FIPS_ENABLED = 0 FIPS_VERSION = 0 ERROR_STRINGS_ENABLED = 1 +ASN_ENABLED = 1 # detect native features based on options.h defines if featureDetection: @@ -91,6 +92,7 @@ if featureDetection: KEYGEN_ENABLED = 1 if '#define WOLFSSL_KEY_GEN' in optionsHeaderStr else 0 PWDBASED_ENABLED = 0 if '#define NO_PWDBASED' in optionsHeaderStr else 1 ERROR_STRINGS_ENABLED = 0 if '#define NO_ERROR_STRINGS' in optionsHeaderStr else 1 + ASN_ENABLED = 0 if '#define NO_ASN' in optionsHeaderStr else 1 if '#define HAVE_FIPS' in optionsHeaderStr: FIPS_ENABLED = 1 @@ -153,6 +155,7 @@ ffibuilder.set_source( int PWDBASED_ENABLED = """ + str(PWDBASED_ENABLED) + """; int FIPS_ENABLED = """ + str(FIPS_ENABLED) + """; int FIPS_VERSION = """ + str(FIPS_VERSION) + """; + int ASN_ENABLED = """ + str(ASN_ENABLED) + """; """, include_dirs=[wolfssl_inc_path()], library_dirs=[wolfssl_lib_path()], @@ -180,6 +183,7 @@ _cdef = """ extern int PWDBASED_ENABLED; extern int FIPS_ENABLED; extern int FIPS_VERSION; + extern int ASN_ENABLED; typedef unsigned char byte; typedef unsigned int word32; @@ -452,6 +456,28 @@ if PWDBASED_ENABLED: int typeH); """ +if ASN_ENABLED: + _cdef += """ + static const long PRIVATEKEY_TYPE; + static const long PUBLICKEY_TYPE; + static const long CERT_TYPE; + + typedef struct DerBuffer { + byte* buffer; + void* heap; + word32 length; + int type; + int dynType; + } DerBuffer; + typedef struct { ...; } EncryptedInfo; + + int wc_PemToDer(const unsigned char* buff, long longSz, int type, + DerBuffer** pDer, void* heap, EncryptedInfo* info, + int* keyFormat); + int wc_DerToPemEx(const byte* der, word32 derSz, byte* output, word32 outSz, + byte *cipher_info, int type); + """ + ffibuilder.cdef(_cdef) if __name__ == "__main__": diff --git a/src/wolfcrypt/asn.py b/src/wolfcrypt/asn.py new file mode 100644 index 0000000..5ad3218 --- /dev/null +++ b/src/wolfcrypt/asn.py @@ -0,0 +1,54 @@ +# asn.py +# +# Copyright (C) 2006-2020 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +# pylint: disable=no-member,no-name-in-module + +from wolfcrypt._ffi import ffi as _ffi +from wolfcrypt._ffi import lib as _lib + +from wolfcrypt.exceptions import WolfCryptError + + +if _lib.ASN_ENABLED: + def pem_to_der(pem, pem_type): + der = _ffi.new("DerBuffer**") + ret = _lib.wc_PemToDer(pem, len(pem), pem_type, der, _ffi.NULL, + _ffi.NULL, _ffi.NULL) + if ret != 0: + err = "Error converting from PEM to DER. ({})".format(ret) + raise WolfCryptError(err) + + return _ffi.buffer(der[0][0].buffer, der[0][0].length)[:] + + def der_to_pem(der, pem_type): + pem_length = _lib.wc_DerToPemEx(der, len(der), _ffi.NULL, 0, _ffi.NULL, + pem_type) + if pem_length <= 0: + err = "Error getting required PEM buffer length. ({})".format(pem_length) + raise WolfCryptError(err) + + pem = _ffi.new("byte[%d]" % pem_length) + pem_length = _lib.wc_DerToPemEx(der, len(der), pem, pem_length, + _ffi.NULL, pem_type) + if pem_length <= 0: + err = "Error converting from DER to PEM. ({})".format(pem_length) + raise WolfCryptError(err) + + return _ffi.buffer(pem, pem_length)[:] diff --git a/src/wolfcrypt/ciphers.py b/src/wolfcrypt/ciphers.py index 6d4a387..2bd3f6e 100644 --- a/src/wolfcrypt/ciphers.py +++ b/src/wolfcrypt/ciphers.py @@ -24,6 +24,7 @@ from wolfcrypt._ffi import ffi as _ffi from wolfcrypt._ffi import lib as _lib from wolfcrypt.utils import t2b from wolfcrypt.random import Random +from wolfcrypt.asn import pem_to_der from wolfcrypt.exceptions import WolfCryptError @@ -359,6 +360,12 @@ if _lib.RSA_ENABLED: raise WolfCryptError("Invalid key error (%d)" % self.output_size) + if _lib.ASN_ENABLED: + @classmethod + def from_pem(cls, file): + der = pem_to_der(file, _lib.PUBLICKEY_TYPE) + return cls(der) + def encrypt(self, plaintext): """ Encrypts **plaintext**, using the public key data in the @@ -455,6 +462,12 @@ if _lib.RSA_ENABLED: raise WolfCryptError("Invalid key size error (%d)" % self.output_size) + if _lib.ASN_ENABLED: + @classmethod + def from_pem(cls, file): + der = pem_to_der(file, _lib.PRIVATEKEY_TYPE) + return cls(der) + if _lib.KEYGEN_ENABLED: def encode_key(self): """ diff --git a/src/wolfcrypt/utils.py b/src/wolfcrypt/utils.py index dd9b511..c29c7cc 100644 --- a/src/wolfcrypt/utils.py +++ b/src/wolfcrypt/utils.py @@ -31,7 +31,7 @@ _BINARY_TYPE = bytes if _PY3 else str def t2b(string): """ - Converts text to bynary. + Converts text to binary. """ if isinstance(string, _BINARY_TYPE): return string diff --git a/tests/certs/server-cert.der b/tests/certs/server-cert.der new file mode 100644 index 0000000..041eba2 Binary files /dev/null and b/tests/certs/server-cert.der differ diff --git a/tests/certs/server-cert.pem b/tests/certs/server-cert.pem new file mode 100644 index 0000000..5b168a4 --- /dev/null +++ b/tests/certs/server-cert.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE3TCCA8WgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBlDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgMB01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xETAPBgNVBAoMCFNh +d3Rvb3RoMRMwEQYDVQQLDApDb25zdWx0aW5nMRgwFgYDVQQDDA93d3cud29sZnNz +bC5jb20xHzAdBgkqhkiG9w0BCQEWEGluZm9Ad29sZnNzbC5jb20wHhcNMjEwMjEw +MTk0OTUzWhcNMjMxMTA3MTk0OTUzWjCBkDELMAkGA1UEBhMCVVMxEDAOBgNVBAgM +B01vbnRhbmExEDAOBgNVBAcMB0JvemVtYW4xEDAOBgNVBAoMB3dvbGZTU0wxEDAO +BgNVBAsMB1N1cHBvcnQxGDAWBgNVBAMMD3d3dy53b2xmc3NsLmNvbTEfMB0GCSqG +SIb3DQEJARYQaW5mb0B3b2xmc3NsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMCVCOFXQfJxbbfSRUEnAWXGRa7yvCQwuJXOL07W9hyIvHyf+6hn +f/5cnFF194rKB+c1L4/hvXvAL3yrZKgX/Mpde7rgIeVyLm8uhtiVc9qsG1O5Xz/X +GQ0lT+FjY1GLC2Q/rUO4pRxcNLOuAKBjxfZ/C1loeHOmjBipAm2vwxkBLrgQ48bM +QLRpo0YzaYduxLsXpvPo3a1zvHsvIbX9ZlEMvVSz4W1fHLwjc9EJA4kU0hC5ZMMq +0KGWSrzh1Bpbx6DAwWN4D0Q3MDKWgDIjlaF3uhPSl3PiXSXJag3DOWCktLBpQkIJ +6dgIvDMgs1gip6rrxOHmYYPF0pbf2dBPrdcCAwEAAaOCATowggE2MB0GA1UdDgQW +BBSzETLJkpiE4sn40DtuA0LKHw6OPDCByQYDVR0jBIHBMIG+gBQnjmcRdMMmHT/t +M2OzpNgdMOXo1aGBmqSBlzCBlDELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB01vbnRh +bmExEDAOBgNVBAcMB0JvemVtYW4xETAPBgNVBAoMCFNhd3Rvb3RoMRMwEQYDVQQL +DApDb25zdWx0aW5nMRgwFgYDVQQDDA93d3cud29sZnNzbC5jb20xHzAdBgkqhkiG +9w0BCQEWEGluZm9Ad29sZnNzbC5jb22CCQCq0z+sGAo3TTAMBgNVHRMEBTADAQH/ +MBwGA1UdEQQVMBOCC2V4YW1wbGUuY29thwR/AAABMB0GA1UdJQQWMBQGCCsGAQUF +BwMBBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAGw2mRJMNDgw1KCZAMdLr +JkxHWxn7rf469TA6KNeqaaQV5yZutzNWrI80PfMhL1NYkdA+tDlIv5MRdDbTh0nD +NA0wMKv0TCcZ1cQMrUm9kfjansgtKqzidY6qCNm/Zf+jsU/wYG9NlcQGf69maiM7 +OqRhtmzKvuGwd/Psg9WMHYV/jXTI7B5J7FdKzP3iOj5UUK5nzRewZ6VTf8MOPqdY +6N/VDPJk860ScOO5QrwIYHbVDKUxd1DgyPM6PUXPMnXvEN217W7SLVeClTi8fVTE +hF77foP18S2cmKxz46fSAjDWHwYe0Nw6rPTCwr5yQJrqzzUhO1Zt4VLygNc1g5cH +zA== +-----END CERTIFICATE----- diff --git a/tests/certs/server-key.der b/tests/certs/server-key.der new file mode 100644 index 0000000..868f054 Binary files /dev/null and b/tests/certs/server-key.der differ diff --git a/tests/certs/server-key.pem b/tests/certs/server-key.pem new file mode 100644 index 0000000..d1627f4 --- /dev/null +++ b/tests/certs/server-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAwJUI4VdB8nFtt9JFQScBZcZFrvK8JDC4lc4vTtb2HIi8fJ/7 +qGd//lycUXX3isoH5zUvj+G9e8AvfKtkqBf8yl17uuAh5XIuby6G2JVz2qwbU7lf +P9cZDSVP4WNjUYsLZD+tQ7ilHFw0s64AoGPF9n8LWWh4c6aMGKkCba/DGQEuuBDj +xsxAtGmjRjNph27Euxem8+jdrXO8ey8htf1mUQy9VLPhbV8cvCNz0QkDiRTSELlk +wyrQoZZKvOHUGlvHoMDBY3gPRDcwMpaAMiOVoXe6E9KXc+JdJclqDcM5YKS0sGlC +Qgnp2Ai8MyCzWCKnquvE4eZhg8XSlt/Z0E+t1wIDAQABAoIBAQCa0DQPUmIFUAHv +n+1kbsLE2hryhNeSEEiSxOlq64t1bMZ5OPLJckqGZFSVd8vDmp231B2kAMieTuTd +x7pnFsF0vKnWlI8rMBr77d8hBSPZSjm9mGtlmrjcxH3upkMVLj2+HSJgKnMw1T7Y +oqyGQy7E9WReP4l1DxHYUSVOn9iqo85gs+KK2X4b8GTKmlsFC1uqy+XjP24yIgXz +0PrvdFKB4l90073/MYNFdfpjepcu1rYZxpIm5CgGUFAOeC6peA0Ul7QS2DFAq6EB +QcIw+AdfFuRhd9Jg8p+N6PS662PeKpeB70xs5lU0USsoNPRTHMRYCj+7r7X3SoVD +LTzxWFiBAoGBAPIsVHY5I2PJEDK3k62vvhl1loFk5rW4iUJB0W3QHBv4G6xpyzY8 +ZH3c9Bm4w2CxV0hfUk9ZOlV/MsAZQ1A/rs5vF/MOn0DKTq0VO8l56cBZOHNwnAp8 +yTpIMqfYSXUKhcLC/RVz2pkJKmmanwpxv7AEpox6Wm9IWlQ7xrFTF9/nAoGBAMuT +3ncVXbdcXHzYkKmYLdZpDmOzo9ymzItqpKISjI57SCyySzfcBhh96v52odSh6T8N +zRtfr1+elltbD6F8r7ObkNtXczrtsCNErkFPHwdCEyNMy/r0FKTV9542fFufqDzB +hV900jkt/9CE3/uzIHoumxeu5roLrl9TpFLtG8SRAoGBAOyY2rvV/vlSSn0CVUlv +VW5SL4SjK7OGYrNU0mNS2uOIdqDvixWl0xgUcndex6MEH54ZYrUbG57D8rUy+UzB +qusMJn3UX0pRXKRFBnBEp1bA1CIUdp7YY1CJkNPiv4GVkjFBhzkaQwsYpVMfORpf +H0O8h2rfbtMiAP4imHBOGhkpAoGBAIpBVihRnl/Ungs7mKNU8mxW1KrpaTOFJAza +1AwtxL9PAmk4fNTm3Ezt1xYRwz4A58MmwFEC3rt1nG9WnHrzju/PisUr0toGakTJ +c/5umYf4W77xfOZltU9s8MnF/xbKixsX4lg9ojerAby/QM5TjI7t7+5ZneBj5nxe +9Y5L8TvBAoGATUX5QIzFW/QqGoq08hysa+kMVja3TnKW1eWK0uL/8fEYEz2GCbjY +dqfJHHFSlDBD4PF4dP1hG0wJzOZoKnGtHN9DvFbbpaS+NXCkXs9P/ABVmTo9I89n +WvUi+LUp0EQR6zUuRr79jhiyX6i/GTKh9dwD5nyaHwx8qbAOITc78bA= +-----END RSA PRIVATE KEY----- diff --git a/tests/certs/server-keyPub.pem b/tests/certs/server-keyPub.pem new file mode 100644 index 0000000..c90f5c4 --- /dev/null +++ b/tests/certs/server-keyPub.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwJUI4VdB8nFtt9JFQScB +ZcZFrvK8JDC4lc4vTtb2HIi8fJ/7qGd//lycUXX3isoH5zUvj+G9e8AvfKtkqBf8 +yl17uuAh5XIuby6G2JVz2qwbU7lfP9cZDSVP4WNjUYsLZD+tQ7ilHFw0s64AoGPF +9n8LWWh4c6aMGKkCba/DGQEuuBDjxsxAtGmjRjNph27Euxem8+jdrXO8ey8htf1m +UQy9VLPhbV8cvCNz0QkDiRTSELlkwyrQoZZKvOHUGlvHoMDBY3gPRDcwMpaAMiOV +oXe6E9KXc+JdJclqDcM5YKS0sGlCQgnp2Ai8MyCzWCKnquvE4eZhg8XSlt/Z0E+t +1wIDAQAB +-----END RSA PUBLIC KEY----- diff --git a/tests/test_asn.py b/tests/test_asn.py new file mode 100644 index 0000000..d0f5925 --- /dev/null +++ b/tests/test_asn.py @@ -0,0 +1,47 @@ +from collections import namedtuple +import pytest +import os +from wolfcrypt._ffi import lib as _lib +from wolfcrypt.utils import h2b + +if _lib.ASN_ENABLED: + from wolfcrypt.asn import pem_to_der, der_to_pem +if _lib.SHA256_ENABLED: + from wolfcrypt.hashes import Sha256 +if _lib.RSA_ENABLED: + from wolfcrypt.ciphers import RsaPrivate, RsaPublic + +certs_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "certs") + +@pytest.fixture +def pem_der_conversion_vectors(): + TestVector = namedtuple("TestVector", "pem der type") + TestVector.__new__.__defaults__ = (None,) * len(TestVector._fields) + + vectors = [] + + if _lib.ASN_ENABLED: + files = [ + ("server-key.pem", "server-key.der", _lib.PRIVATEKEY_TYPE), + ("server-cert.pem", "server-cert.der", _lib.CERT_TYPE), + ] + for f in files: + pem_path = os.path.join(certs_dir, f[0]) + with open(pem_path, "rb") as pem_handle: + pem = pem_handle.read() + + der_path = os.path.join(certs_dir, f[1]) + with open(der_path, "rb") as der_handle: + der = der_handle.read() + + vectors.append(TestVector(pem=pem, der=der, type=f[2])) + + return vectors + +def test_pem_der_conversion(pem_der_conversion_vectors): + for vector in pem_der_conversion_vectors: + computed_der = pem_to_der(vector.pem, vector.type) + assert computed_der == vector.der + + computed_pem = der_to_pem(vector.der, vector.type) + assert computed_pem == vector.pem diff --git a/tests/test_ciphers.py b/tests/test_ciphers.py index 776137c..de70d54 100644 --- a/tests/test_ciphers.py +++ b/tests/test_ciphers.py @@ -25,6 +25,9 @@ import pytest from wolfcrypt._ffi import ffi as _ffi from wolfcrypt._ffi import lib as _lib from wolfcrypt.utils import t2b, h2b +import os + +certs_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "certs") if _lib.DES3_ENABLED: from wolfcrypt.ciphers import Des3 @@ -54,7 +57,8 @@ from wolfcrypt.ciphers import ( @pytest.fixture def vectors(): - TestVector = namedtuple("TestVector", "key iv plaintext ciphertext raw_key pkcs8_key") + TestVector = namedtuple("TestVector", """key iv plaintext ciphertext raw_key + pkcs8_key pem""") TestVector.__new__.__defaults__ = (None,) * len(TestVector._fields) # test vector dictionary @@ -88,7 +92,8 @@ def vectors(): "905F3ED9E4D5DF94CAC1A9D719DA86C9E84DC4613682FEABAD7E7725BB8D" "11A5BC623AA838CC39A20466B4F7F7F3AADA4D020EBB5E8D6948DC77C928" "0E22E96BA426BA4CE8C1FD4A6F2B1FEF8AAEF69062E5641EEB2B3C67C8DC" - "2700F6916865A90203010001") + "2700F6916865A90203010001"), + pem=os.path.join(certs_dir, "server-keyPub.pem") ) vectorArray[RsaPrivate]=TestVector( key=h2b( @@ -153,7 +158,8 @@ def vectors(): "30bab7488c48140ef49f7e779743e1b4" "19353123759c3b44ad691256ee006164" "1666d37c742b15b4a2febf086b1a5d3f" - "9012b105863129dbd9e2") + "9012b105863129dbd9e2"), + pem=os.path.join(certs_dir, "server-key.pem") ) if _lib.ECC_ENABLED: @@ -323,6 +329,18 @@ if _lib.RSA_ENABLED: def rsa_public(vectors): return RsaPublic(vectors[RsaPublic].key) + @pytest.fixture + def rsa_private_pem(vectors): + with open(vectors[RsaPrivate].pem, "rb") as f: + pem = f.read() + return RsaPrivate.from_pem(pem) + + @pytest.fixture + def rsa_public_pem(vectors): + with open(vectors[RsaPublic].pem, "rb") as f: + pem = f.read() + return RsaPublic.from_pem(pem) + def test_new_rsa_raises(vectors): with pytest.raises(WolfCryptError): @@ -385,6 +403,22 @@ if _lib.RSA_ENABLED: assert 1024 / 8 == len(signature) == rsa_private.output_size assert plaintext == rsa_private.verify(signature) + def test_rsa_sign_verify_pem(rsa_private_pem, rsa_public_pem): + plaintext = t2b("Everyone gets Friday off.") + + # normal usage, sign with private, verify with public + signature = rsa_private_pem.sign(plaintext) + + assert 256 == len(signature) == rsa_private_pem.output_size + assert plaintext == rsa_public_pem.verify(signature) + + # private object holds both private and public info, so it can also verify + # using the known public key. + signature = rsa_private_pem.sign(plaintext) + + assert 256 == len(signature) == rsa_private_pem.output_size + assert plaintext == rsa_private_pem.verify(signature) + def test_rsa_pkcs8_sign_verify(rsa_private_pkcs8, rsa_public): plaintext = t2b("Everyone gets Friday off.")