diff --git a/.github/ci-deps/packages-ubuntu-24.04-full.txt b/.github/ci-deps/packages-ubuntu-24.04-full.txt index 9e4cbc106cf..dc14cb6b339 100644 --- a/.github/ci-deps/packages-ubuntu-24.04-full.txt +++ b/.github/ci-deps/packages-ubuntu-24.04-full.txt @@ -9,6 +9,7 @@ autoconf-archive automake autopoint bc +bison bubblewrap build-essential ccache @@ -16,6 +17,7 @@ clang clang-14 clang-19 cmake +flex g++-10 g++-11 g++-12 @@ -77,6 +79,7 @@ libsqlite3-dev libssl-dev libtool liburcu-dev +libusb-1.0-0-dev libuv1-dev linux-libc-dev make @@ -88,6 +91,7 @@ ninja-build pkg-config pkgconf psmisc +python3-cryptography python3-docutils python3-impacket python3-ldb diff --git a/.github/scripts/idevice-emulator.py b/.github/scripts/idevice-emulator.py new file mode 100755 index 00000000000..3a8c2a35112 --- /dev/null +++ b/.github/scripts/idevice-emulator.py @@ -0,0 +1,559 @@ +#!/usr/bin/env python3 +# Emulated iOS device for the libimobiledevice CI job. +# +# libimobiledevice never speaks USB: it talks to usbmuxd over a Unix socket, +# and libusbmuxd 2.x uses the one named by USBMUXD_SOCKET_ADDRESS=UNIX:. +# This serves that socket plus the lockdownd service usbmuxd tunnels to, so +# the unmodified tools run a real pair / session exchange with no hardware. +# +# Two protocols, both plists on a socket: +# +# usbmuxd 16-byte little-endian header (length including the header, +# version, message type, tag) followed by an XML plist. +# See usbmuxd/src/usbmuxd-proto.h and src/client.c. +# lockdownd 4-byte big-endian length followed by an XML plist, on the +# connection usbmuxd tunnels to port 62078. +# See libimobiledevice/src/lockdown.c. +# +# The device RSA key is generated at startup. The host reads its public half +# through GetValue DevicePublicKey, issues the pair record certificates from +# it, and sends them in the Pair request; this checks those certificates and +# then serves the session TLS with the DeviceCertificate it was handed, +# requiring the host to present the RootCertificate as its client +# certificate - which is what libimobiledevice sends (src/idevice.c). + +import argparse +import asyncio +import contextlib +import datetime +import errno +import os +import plistlib +import shutil +import signal +import socket +import ssl +import struct +import sys +import tempfile +import uuid +import warnings + +from cryptography import x509 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import padding, rsa + +# usbmuxd framing and constants (usbmuxd/src/usbmuxd-proto.h). +USBMUX_HEADER = struct.Struct('I') +LOCKDOWN_PORT = 62078 +LOCKDOWN_TYPE = 'com.apple.mobile.lockdown' + +# pair_record_generate_keys_and_certs() dates the certificates ten years out. +MIN_DEVICE_CERT_LIFETIME = datetime.timedelta(days=9 * 365) + + +class Log: + """One line per event, for the CI job to assert on.""" + + def __init__(self, path): + self._file = open(path, 'w', buffering=1, encoding='utf-8') + + def __call__(self, message): + stamp = datetime.datetime.now().strftime('%H:%M:%S.%f')[:-3] + self._file.write('%s %s\n' % (stamp, message)) + + def close(self): + self._file.close() + + +def not_valid_after(cert): + """cryptography 42 deprecated the naive not_valid_after.""" + value = getattr(cert, 'not_valid_after_utc', None) + if value is None: + value = cert.not_valid_after.replace(tzinfo=datetime.timezone.utc) + return value + + +def subject_text(cert): + """The tools' certificates carry an empty subject DN.""" + return cert.subject.rfc4514_string() or '' + + +class Device: + """Everything the emulated device knows about itself.""" + + def __init__(self, args): + self.udid = args.udid + self.device_id = 1 + self.buid = str(uuid.uuid4()).upper() + self.key = rsa.generate_private_key(public_exponent=65537, + key_size=2048) + # lockdownd hands out the device public key in PKCS#1 form; that is + # what pair_record_generate_keys_and_certs() reads back with + # PEM_read_bio_RSAPublicKey(). + self.public_key_pem = self.key.public_key().public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.PKCS1) + self.values = { + None: { + 'BuildVersion': '22G86', + 'CPUArchitecture': 'arm64e', + 'DeviceClass': 'iPhone', + 'DeviceColor': '1', + 'DeviceName': args.device_name, + 'DevicePublicKey': self.public_key_pem, + 'HardwareModel': 'D73AP', + 'HardwarePlatform': 't8110', + 'ProductName': 'iPhone OS', + 'ProductType': args.product_type, + 'ProductVersion': args.product_version, + 'ProtocolVersion': '2', + 'SerialNumber': 'F2LX90ABCDEF', + 'TimeIntervalSince1970': 0, + 'TimeZone': 'Etc/UTC', + 'UniqueDeviceID': self.udid, + 'WiFiAddress': args.wifi_address, + }, + } + # The pair record the device itself keeps, as set by Pair and dropped + # by Unpair. Separate from the host-side records usbmuxd stores. + self.pair_record = None + + +class Emulator: + def __init__(self, args, log): + self.log = log + self.device = Device(args) + # The host-side pair record store usbmuxd keeps, keyed by UDID. + self.records = {} + self._tmpdir = tempfile.mkdtemp(prefix='idevice-emulator-') + + def close(self): + shutil.rmtree(self._tmpdir, ignore_errors=True) + + # ---- usbmuxd ----------------------------------------------------------- + + def _attached(self): + return { + 'MessageType': 'Attached', + 'DeviceID': self.device.device_id, + 'Properties': { + 'ConnectionSpeed': 480000000, + 'ConnectionType': 'USB', + 'DeviceID': self.device.device_id, + 'LocationID': 0, + 'ProductID': 0x12a8, + 'SerialNumber': self.device.udid, + }, + } + + @staticmethod + def _send_usbmux(writer, tag, payload): + body = plistlib.dumps(payload) + writer.write(USBMUX_HEADER.pack(USBMUX_HEADER.size + len(body), + USBMUX_VERSION_PLIST, + USBMUX_MESSAGE_PLIST, tag) + body) + + def _send_result(self, writer, tag, number): + self._send_usbmux(writer, tag, {'MessageType': 'Result', + 'Number': number}) + + async def handle_usbmux(self, reader, writer): + try: + while True: + header = await reader.readexactly(USBMUX_HEADER.size) + length, _, message, tag = USBMUX_HEADER.unpack(header) + payload = await reader.readexactly( + max(0, length - USBMUX_HEADER.size)) + if message != USBMUX_MESSAGE_PLIST or not payload: + self.log('usbmux: message type %d -> BadCommand' % message) + self._send_result(writer, tag, RESULT_BADCOMMAND) + continue + if await self._usbmux_request(reader, writer, tag, + plistlib.loads(payload)): + return + await writer.drain() + except (asyncio.IncompleteReadError, ConnectionResetError, + BrokenPipeError): + pass + finally: + with contextlib.suppress(OSError): + writer.close() + + async def _usbmux_request(self, reader, writer, tag, request): + """Answer one usbmuxd request; True means the connection was taken + over by the tunnelled lockdownd session.""" + message = request.get('MessageType') + record_id = request.get('PairRecordID') + + if message == 'ListDevices': + self._send_usbmux(writer, tag, {'DeviceList': [self._attached()]}) + self.log('usbmux: ListDevices -> 1 device %s' % self.device.udid) + elif message == 'Listen': + self._send_result(writer, tag, RESULT_OK) + self._send_usbmux(writer, 0, self._attached()) + self.log('usbmux: Listen -> Attached DeviceID=%d' + % self.device.device_id) + elif message == 'ReadBUID': + self._send_usbmux(writer, tag, {'BUID': self.device.buid}) + self.log('usbmux: ReadBUID -> %s' % self.device.buid) + elif message == 'ReadPairRecord': + data = self.records.get(record_id) + if data is None: + self._send_result(writer, tag, RESULT_NOENT) + self.log('usbmux: ReadPairRecord %s -> not found (ENOENT)' + % record_id) + else: + self._send_usbmux(writer, tag, {'PairRecordData': data}) + self.log('usbmux: ReadPairRecord %s -> %d bytes' + % (record_id, len(data))) + elif message == 'SavePairRecord': + data = request['PairRecordData'] + try: + detail = self._check_saved_record(data) + except (ValueError, KeyError, TypeError) as exc: + self._send_result(writer, tag, RESULT_BADCOMMAND) + self.log('pair: stored pair record REJECTED: %s' % exc) + return False + self.records[record_id] = data + self._send_result(writer, tag, RESULT_OK) + self.log('usbmux: SavePairRecord %s -> stored %d bytes, %s' + % (record_id, len(data), detail)) + elif message == 'DeletePairRecord': + existed = self.records.pop(record_id, None) is not None + self._send_result(writer, tag, RESULT_OK) + self.log('usbmux: DeletePairRecord %s -> %s' % ( + record_id, 'deleted' if existed else 'no such record')) + elif message == 'Connect': + return await self._usbmux_connect(reader, writer, tag, request) + else: + self._send_result(writer, tag, RESULT_BADCOMMAND) + self.log('usbmux: %s -> BadCommand' % message) + return False + + async def _usbmux_connect(self, reader, writer, tag, request): + # PortNumber travels in network byte order. + port = socket.ntohs(request.get('PortNumber', 0) & 0xffff) + device_id = request.get('DeviceID') + if device_id != self.device.device_id: + self._send_result(writer, tag, RESULT_BADDEV) + self.log('usbmux: Connect DeviceID=%s -> BadDevice' % device_id) + return False + if port != LOCKDOWN_PORT: + self._send_result(writer, tag, RESULT_CONNREFUSED) + self.log('usbmux: Connect port=%d -> ConnectionRefused' % port) + return False + self._send_result(writer, tag, RESULT_OK) + await writer.drain() + self.log('usbmux: Connect DeviceID=%d port=%d -> lockdownd' + % (device_id, port)) + # From here the socket carries the device stream verbatim, so the + # lockdownd handler takes it over for the rest of the connection. + await self.serve_lockdown(reader, writer) + return True + + # ---- lockdownd --------------------------------------------------------- + + @staticmethod + async def _send_lockdown(writer, reply): + body = plistlib.dumps(reply) + writer.write(LOCKDOWN_HEADER.pack(len(body)) + body) + await writer.drain() + + async def serve_lockdown(self, reader, writer): + session_id = None + try: + while True: + header = await reader.readexactly(LOCKDOWN_HEADER.size) + (length,) = LOCKDOWN_HEADER.unpack(header) + request = plistlib.loads(await reader.readexactly(length)) + name = request.get('Request') + tail = ' [session %s]' % session_id if session_id else '' + + if name == 'QueryType': + reply = {'Request': name, 'Type': LOCKDOWN_TYPE} + self.log('lockdown: QueryType -> %s%s' + % (LOCKDOWN_TYPE, tail)) + elif name == 'GetValue': + reply = self._get_value(request, tail) + elif name in ('Pair', 'ValidatePair', 'Unpair'): + reply = self._pairing(name, request) + elif name == 'StartSession': + reply = self._start_session(request) + elif name == 'StopSession': + reply = {'Request': name, 'Result': 'Success'} + self.log('lockdown: StopSession%s -> Success' % tail) + session_id = None + elif name == 'StartService': + # No services are emulated. + reply = {'Request': name, 'Error': 'InvalidService'} + self.log('lockdown: StartService %s -> InvalidService%s' + % (request.get('Service'), tail)) + elif name == 'Goodbye': + reply = {'Request': name, 'Result': 'Success'} + self.log('lockdown: Goodbye -> Success%s' % tail) + await self._send_lockdown(writer, reply) + return + else: + reply = {'Request': name, 'Error': 'InvalidRequest'} + self.log('lockdown: %s -> InvalidRequest%s' % (name, tail)) + + await self._send_lockdown(writer, reply) + + if reply.get('EnableSessionSSL'): + session_id = reply['SessionID'] + reader, writer = await self._enable_ssl(reader, writer) + except (asyncio.IncompleteReadError, ConnectionResetError, + BrokenPipeError, ssl.SSLError): + pass + + def _get_value(self, request, tail): + domain = request.get('Domain') + key = request.get('Key') + values = self.device.values.get(domain, {}) + reply = {'Request': 'GetValue'} + if domain is not None: + reply['Domain'] = domain + if key is not None: + reply['Key'] = key + if key is None: + reply['Value'] = values + shown = '%d values' % len(values) + elif key in values: + reply['Value'] = values[key] + shown = repr(values[key])[:60] + else: + reply['Error'] = 'MissingValue' + shown = 'MissingValue' + self.log('lockdown: GetValue domain=%s key=%s -> %s%s' + % (domain or '-', key or '-', shown, tail)) + return reply + + def _pairing(self, name, request): + record = request.get('PairRecord') or {} + host_id = record.get('HostID') + if name == 'Unpair': + self.device.pair_record = None + self.log('lockdown: Unpair HostID=%s -> Success, device pair ' + 'record dropped' % host_id) + return {'Request': name, 'Result': 'Success'} + if name == 'ValidatePair': + known = self.device.pair_record + if not known or known.get('HostID') != host_id: + self.log('lockdown: ValidatePair HostID=%s -> InvalidHostID' + % host_id) + return {'Request': name, 'Error': 'InvalidHostID'} + self.log('lockdown: ValidatePair HostID=%s -> Success' % host_id) + return {'Request': name, 'Result': 'Success'} + + self.log('lockdown: Pair HostID=%s SystemBUID=%s' + % (host_id, record.get('SystemBUID'))) + try: + self.log('pair: pair record verified: %s' % self._check_record( + record)) + except (ValueError, KeyError, TypeError, + x509.ExtensionNotFound) as exc: + self.log('pair: pair record REJECTED: %s' % exc) + return {'Request': name, 'Error': 'InvalidPairRecord'} + self.device.pair_record = record + # A real device answers a successful pairing with an escrow bag. + return {'Request': name, 'Result': 'Success', + 'EscrowBag': os.urandom(64)} + + def _check_record(self, record): + """Check the certificates the host generated for this device.""" + root = x509.load_pem_x509_certificate(record['RootCertificate']) + host = x509.load_pem_x509_certificate(record['HostCertificate']) + device = x509.load_pem_x509_certificate(record['DeviceCertificate']) + if not record.get('HostID') or not record.get('SystemBUID'): + raise ValueError('HostID or SystemBUID missing') + + for name, cert in (('root', root), ('host', host), + ('device', device)): + # Every certificate is signed by the root private key, and the + # root signs itself. + try: + root.public_key().verify(cert.signature, + cert.tbs_certificate_bytes, + padding.PKCS1v15(), + cert.signature_hash_algorithm) + except Exception: + raise ValueError('%s certificate does not chain to the root' + % name) + + if not root.extensions.get_extension_for_class( + x509.BasicConstraints).value.ca: + raise ValueError('root certificate is not a CA') + for name, cert in (('host', host), ('device', device)): + if cert.extensions.get_extension_for_class( + x509.BasicConstraints).value.ca: + raise ValueError('%s certificate is a CA' % name) + usage = cert.extensions.get_extension_for_class( + x509.KeyUsage).value + if not (usage.digital_signature and usage.key_encipherment + and not usage.content_commitment + and not usage.data_encipherment + and not usage.key_agreement + and not usage.key_cert_sign and not usage.crl_sign): + raise ValueError('%s certificate key usage is %s' + % (name, usage)) + + public_key = self.device.key.public_key() + if device.public_key().public_numbers() != public_key.public_numbers(): + raise ValueError('device certificate carries a foreign key') + # RFC 5280 method 1: the SHA-1 of the public key bit string. + expected = x509.SubjectKeyIdentifier.from_public_key(public_key) + found = device.extensions.get_extension_for_class( + x509.SubjectKeyIdentifier).value + if found.digest != expected.digest: + raise ValueError('device certificate subject key identifier %s ' + 'does not match the device key' + % found.digest.hex()) + + lifetime = not_valid_after(device) - datetime.datetime.now( + datetime.timezone.utc) + if lifetime < MIN_DEVICE_CERT_LIFETIME: + raise ValueError('device certificate expires in %s' % lifetime) + + return ('root is a CA and self-signed (%s), host and device ' + 'certificates chain to it, neither is a CA, both carry ' + 'digitalSignature+keyEncipherment, device SKI %s matches the ' + 'device key, device certificate valid for %d days' + % (root.signature_hash_algorithm.name, + found.digest.hex(), lifetime.days)) + + def _check_saved_record(self, data): + """Check the record the host stores for itself. + + The Pair request strips the private keys out, so they are only + visible here; libimobiledevice reads the root key back out of this + record to authenticate the session TLS (src/idevice.c). + """ + record = plistlib.loads(data) + for name in ('Root', 'Host'): + cert = x509.load_pem_x509_certificate(record[name + + 'Certificate']) + key = serialization.load_pem_private_key( + record[name + 'PrivateKey'], None) + if (key.public_key().public_numbers() + != cert.public_key().public_numbers()): + raise ValueError('%sPrivateKey does not match %sCertificate' + % (name, name)) + return 'root and host private keys match their certificates' + + def _start_session(self, request): + host_id = request.get('HostID') + known = self.device.pair_record + if not known or known.get('HostID') != host_id: + self.log('lockdown: StartSession HostID=%s -> InvalidHostID' + % host_id) + return {'Request': 'StartSession', 'Error': 'InvalidHostID'} + session_id = str(uuid.uuid4()).upper() + self.log('lockdown: StartSession HostID=%s -> SessionID=%s ' + 'EnableSessionSSL=true' % (host_id, session_id)) + return {'Request': 'StartSession', 'Result': 'Success', + 'SessionID': session_id, 'EnableSessionSSL': True} + + # ---- session TLS ------------------------------------------------------- + + def _ssl_context(self): + record = self.device.pair_record + cert = os.path.join(self._tmpdir, 'device.pem') + with open(cert, 'wb') as handle: + handle.write(record['DeviceCertificate']) + handle.write(self.device.key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.NoEncryption())) + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + with warnings.catch_warnings(): + # The enum member is deprecated, TLS 1.0 itself still works and + # libimobiledevice pins it for devices below iOS 10 + # (src/idevice.c). + warnings.simplefilter('ignore', DeprecationWarning) + context.minimum_version = ssl.TLSVersion.TLSv1 + context.set_ciphers('DEFAULT:@SECLEVEL=0') + # The host presents the RootCertificate as its client certificate. + context.verify_mode = ssl.CERT_REQUIRED + context.load_verify_locations( + cadata=record['RootCertificate'].decode('ascii')) + context.load_cert_chain(cert) + return context + + async def _enable_ssl(self, reader, writer): + # Stop reading before the StartSession reply reaches the host, so its + # ClientHello cannot land in the stream buffer that start_tls() is + # about to replace. + writer.transport.pause_reading() + await writer.start_tls(self._ssl_context()) + session = writer.get_extra_info('ssl_object') + peer = x509.load_der_x509_certificate(session.getpeercert(True)) + root = x509.load_pem_x509_certificate( + self.device.pair_record['RootCertificate']) + self.log('tls: session established version=%s cipher=%s client ' + 'certificate subject=%s (%s)' + % (session.version(), session.cipher()[0], subject_text(peer), + 'matches RootCertificate from the pair record' + if peer == root else 'NOT the RootCertificate')) + return reader, writer + + +async def main_async(args): + log = Log(args.log) + emulator = Emulator(args, log) + if os.path.exists(args.socket): + os.unlink(args.socket) + server = await asyncio.start_unix_server(emulator.handle_usbmux, + path=args.socket) + log('ready: udid=%s ProductVersion=%s socket=%s' + % (emulator.device.udid, args.product_version, args.socket)) + + stop = asyncio.Event() + loop = asyncio.get_running_loop() + for signame in (signal.SIGTERM, signal.SIGINT): + loop.add_signal_handler(signame, stop.set) + await stop.wait() + + log('stopping on signal') + server.close() + await server.wait_closed() + emulator.close() + with contextlib.suppress(OSError): + os.unlink(args.socket) + log.close() + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--socket', default='/tmp/idevice-emulator.sock', + help='Unix socket to serve the usbmuxd protocol on') + parser.add_argument('--log', default='/tmp/idevice-emulator.log', + help='file to write one line per event to') + parser.add_argument('--udid', + default='1e2f3a4b5c6d7e8f90a1b2c3d4e5f60718293a4b', + help='UDID the emulated device reports') + parser.add_argument('--product-version', default='18.6', + help='iOS version the emulated device reports') + parser.add_argument('--product-type', default='iPhone15,2') + parser.add_argument('--device-name', default='CI Emulated Device') + parser.add_argument('--wifi-address', default='00:1a:2b:3c:4d:5e') + args = parser.parse_args() + asyncio.run(main_async(args)) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/.github/workflows/bind.yml b/.github/workflows/bind.yml index 9164461ad4c..2d8c52517cf 100644 --- a/.github/workflows/bind.yml +++ b/.github/workflows/bind.yml @@ -48,7 +48,7 @@ jobs: fail-fast: false matrix: # List of releases to test - ref: [ 9.18.0, 9.18.28, 9.18.33, 9.20.11 ] + ref: [ 9.18.0, 9.18.28, 9.18.33, 9.20.11, 9.20.23 ] name: ${{ matrix.ref }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} runs-on: ubuntu-24.04 diff --git a/.github/workflows/jwt-cpp.yml b/.github/workflows/jwt-cpp.yml index e3263f3f996..d25901feab4 100644 --- a/.github/workflows/jwt-cpp.yml +++ b/.github/workflows/jwt-cpp.yml @@ -50,6 +50,8 @@ jobs: fail-fast: false matrix: config: + - ref: 0.7.2 + runner: ubuntu-24.04 - ref: 0.7.0 runner: ubuntu-24.04 - ref: 0.6.0 diff --git a/.github/workflows/krb5.yml b/.github/workflows/krb5.yml index 80810e9a30b..ffaee69880f 100644 --- a/.github/workflows/krb5.yml +++ b/.github/workflows/krb5.yml @@ -51,7 +51,7 @@ jobs: fail-fast: false matrix: # List of releases to test - ref: [ 1.21.1 ] + ref: [ 1.21.1, 1.22.2 ] name: ${{ matrix.ref }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} runs-on: ubuntu-22.04 diff --git a/.github/workflows/libimobiledevice.yml b/.github/workflows/libimobiledevice.yml new file mode 100644 index 00000000000..6846e96dff0 --- /dev/null +++ b/.github/workflows/libimobiledevice.yml @@ -0,0 +1,224 @@ +name: libimobiledevice Tests + +# START OF COMMON SECTION +on: + push: + branches: [ 'release/**' ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read +# END OF COMMON SECTION + +jobs: + build_wolfssl: + name: Build wolfSSL + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + # Just to keep it the same as the testing target + runs-on: ubuntu-24.04 + # This should be a safe limit for the tests to run. + timeout-minutes: 4 + steps: + - name: Build wolfSSL + uses: wolfSSL/actions-build-autotools-project@v1 + with: + path: wolfssl + configure: --enable-all --enable-tlsv10 + install: true + check: false + + - name: tar build-dir + run: tar -zcf build-dir.tgz build-dir + + - name: Upload built lib + uses: actions/upload-artifact@v6 + with: + name: wolf-install-libimobiledevice + path: build-dir.tgz + retention-days: 5 + + libimobiledevice_check: + strategy: + fail-fast: false + matrix: + # List of releases to test + include: + - ref: 1.4.0 + libplist: 2.7.0 + glue: 1.3.2 + libusbmuxd: 2.1.1 + libtatsu: 1.0.5 + name: ${{ matrix.ref }} + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + runs-on: ubuntu-24.04 + # This should be a safe limit for the tests to run. + timeout-minutes: 15 + needs: build_wolfssl + steps: + - name: Checkout wolfSSL CI actions + uses: actions/checkout@v5 + with: + sparse-checkout: | + .github/actions + .github/scripts + fetch-depth: 1 + + - name: Download lib + uses: actions/download-artifact@v7 + with: + name: wolf-install-libimobiledevice + + - name: untar build-dir + run: tar -xf build-dir.tgz + + - name: Install dependencies + uses: ./.github/actions/install-apt-deps + with: + packages: libusb-1.0-0-dev libcurl4-openssl-dev libreadline-dev python3-cryptography + ghcr-debs-tag: ubuntu-24.04-full + + - name: Checkout OSP + uses: actions/checkout@v5 + with: + repository: wolfssl/osp + path: osp + fetch-depth: 1 + + # The Ubuntu packages are too old (libtatsu needs libplist >= 2.6.0) + # and libimobiledevice-glue/libtatsu are not packaged, so build the + # dependency chain from source into the wolfSSL prefix. None of these + # use OpenSSL or wolfSSL. + - name: Checkout libplist + uses: actions/checkout@v5 + with: + repository: libimobiledevice/libplist + path: libplist + ref: ${{ matrix.libplist }} + fetch-depth: 1 + + - name: Checkout libimobiledevice-glue + uses: actions/checkout@v5 + with: + repository: libimobiledevice/libimobiledevice-glue + path: libimobiledevice-glue + ref: ${{ matrix.glue }} + fetch-depth: 1 + + - name: Checkout libusbmuxd + uses: actions/checkout@v5 + with: + repository: libimobiledevice/libusbmuxd + path: libusbmuxd + ref: ${{ matrix.libusbmuxd }} + fetch-depth: 1 + + - name: Checkout libtatsu + uses: actions/checkout@v5 + with: + repository: libimobiledevice/libtatsu + path: libtatsu + ref: ${{ matrix.libtatsu }} + fetch-depth: 1 + + - name: Checkout libimobiledevice + uses: actions/checkout@v5 + with: + repository: libimobiledevice/libimobiledevice + path: libimobiledevice + ref: ${{ matrix.ref }} + fetch-depth: 1 + + - name: Build dependencies + run: | + export PKG_CONFIG_PATH=$GITHUB_WORKSPACE/build-dir/lib/pkgconfig + for dep in libplist libimobiledevice-glue libusbmuxd libtatsu; do + pushd $dep + ./autogen.sh --prefix=$GITHUB_WORKSPACE/build-dir --without-cython + make -j + make install + popd + done + + - name: Build libimobiledevice + working-directory: libimobiledevice + run: | + export PKG_CONFIG_PATH=$GITHUB_WORKSPACE/build-dir/lib/pkgconfig + patch -p1 < $GITHUB_WORKSPACE/osp/libimobiledevice/${{ matrix.ref }}.patch + ./autogen.sh --prefix=$GITHUB_WORKSPACE/build-dir --enable-wolfssl --without-cython + make -j + make install + + # libimobiledevice has no test suite and a runner has no iOS device. + # .github/scripts/idevice-emulator.py plays one: it serves the usbmuxd + # socket named by USBMUXD_SOCKET_ADDRESS and the lockdownd service + # behind it, checks the pair record the tools generate through wolfSSL + # and requires the session TLS. The second run reports iOS 9, for which + # libimobiledevice pins the session to TLS 1.0 (needs --enable-tlsv10). + - name: Test libimobiledevice + env: + UDID: 1e2f3a4b5c6d7e8f90a1b2c3d4e5f60718293a4b + USBMUXD_SOCKET_ADDRESS: UNIX:/tmp/idevice-emulator.sock + run: | + export LD_LIBRARY_PATH=$GITHUB_WORKSPACE/build-dir/lib + bin=$GITHUB_WORKSPACE/build-dir/bin + ldd $GITHUB_WORKSPACE/build-dir/lib/libimobiledevice-1.0.so | grep wolfssl + ldd $bin/idevicepair | grep wolfssl + + run_against_emulator() { + local version=$1 want_tls=$2 emulator i devices + local log=/tmp/idevice-emulator-$version.log + rm -f /tmp/idevice-emulator.sock + python3 .github/scripts/idevice-emulator.py \ + --socket /tmp/idevice-emulator.sock --log $log \ + --udid $UDID --product-version $version & + emulator=$! + for i in $(seq 1 100); do + [ -S /tmp/idevice-emulator.sock ] && break + sleep 0.1 + done + + # Assign on its own line: a command substitution inside test, + # or inside a `local` declaration, would discard idevice_id's + # exit status. + devices=$($bin/idevice_id -l) + test "$devices" = "$UDID" + $bin/idevicepair pair + $bin/idevicepair validate + $bin/ideviceinfo | grep "^ProductVersion: $version\$" + $bin/idevicepair unpair + + # Also checks that the emulator exits cleanly on SIGTERM. + kill $emulator + wait $emulator + + # What the emulator saw: pair record OK, session over the expected + # TLS version with the RootCertificate as client certificate, + # requests served inside it, unpair reached the record store. + grep -q 'pair: pair record verified: ' $log + grep -q 'private keys match their certificates' $log + grep -qE "tls: session established version=$want_tls" $log + grep -q 'matches RootCertificate from the pair record' $log + grep -q 'lockdown: GetValue .* \[session ' $log + grep -q 'usbmux: DeletePairRecord .* -> deleted' $log + if grep -qE 'REJECTED|NOT the RootCertificate' $log; then + exit 1 + fi + } + + run_against_emulator 18.6 'TLSv1\.[23] ' + run_against_emulator 9.3 'TLSv1 ' + + - name: Emulator logs + if: always() + run: | + for log in /tmp/idevice-emulator-*.log; do + [ -f "$log" ] || continue + echo "=== $log ===" + cat "$log" + done diff --git a/.github/workflows/libspdm.yml b/.github/workflows/libspdm.yml index 66318ba68d7..936656b61f7 100644 --- a/.github/workflows/libspdm.yml +++ b/.github/workflows/libspdm.yml @@ -48,7 +48,7 @@ jobs: fail-fast: false matrix: # List of releases to test - ref: [ 3.7.0 ] + ref: [ 3.7.0, 3.8.2 ] name: ${{ matrix.ref }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} runs-on: ubuntu-24.04 diff --git a/.github/workflows/libvncserver.yml b/.github/workflows/libvncserver.yml index 18a0e27ffcf..7d73af91904 100644 --- a/.github/workflows/libvncserver.yml +++ b/.github/workflows/libvncserver.yml @@ -48,7 +48,7 @@ jobs: strategy: fail-fast: false matrix: - ref: [ 0.9.13, 0.9.14 ] + ref: [ 0.9.13, 0.9.14, 0.9.15 ] name: ${{ matrix.ref }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} runs-on: ubuntu-24.04 diff --git a/.github/workflows/msmtp.yml b/.github/workflows/msmtp.yml index 1596a8da22e..fff780f542d 100644 --- a/.github/workflows/msmtp.yml +++ b/.github/workflows/msmtp.yml @@ -46,7 +46,7 @@ jobs: strategy: fail-fast: false matrix: - ref: [ 1.8.28 ] + ref: [ 1.8.28, 1.8.32 ] name: ${{ matrix.ref }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} runs-on: ubuntu-24.04 diff --git a/.github/workflows/net-snmp.yml b/.github/workflows/net-snmp.yml index 78a0a6238a1..e5aa69a6e14 100644 --- a/.github/workflows/net-snmp.yml +++ b/.github/workflows/net-snmp.yml @@ -49,6 +49,11 @@ jobs: # List of releases to test include: - ref: 5.9.3 + patch_file: $GITHUB_WORKSPACE/osp/net-snmp/5.9.3.patch + test_opts: -e 'agentxperl' + # 5.9.5+ has wolfSSL support upstream, no patch needed + - ref: 5.9.5.2 + patch_file: '' test_opts: -e 'agentxperl' name: ${{ matrix.ref }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} @@ -78,7 +83,7 @@ jobs: repository: net-snmp/net-snmp ref: v${{ matrix.ref }} path: net-snmp - patch-file: $GITHUB_WORKSPACE/osp/net-snmp/${{ matrix.ref }}.patch + patch-file: ${{ matrix.patch_file }} configure: --disable-shared --with-wolfssl=$GITHUB_WORKSPACE/build-dir check: false diff --git a/.github/workflows/openldap.yml b/.github/workflows/openldap.yml index e681022b990..f5999888bc4 100644 --- a/.github/workflows/openldap.yml +++ b/.github/workflows/openldap.yml @@ -55,6 +55,8 @@ jobs: git_ref: OPENLDAP_REL_ENG_2_6_7 - osp_ref: 2.6.9 git_ref: OPENLDAP_REL_ENG_2_6_9 + - osp_ref: 2.6.13 + git_ref: OPENLDAP_REL_ENG_2_6_13 name: ${{ matrix.osp_ref }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} runs-on: ubuntu-24.04 diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 95d33be9965..7255df7bf9d 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -96,6 +96,22 @@ jobs: test_urllib2_localnet test_xmlrpc test_docxmlrpc + - python_ver: 3.14.5 + tests: >- + test_ssl + test.test_asyncio.test_ssl + test.test_asyncio.test_sslproto + test_hashlib + test_hmac + test_secrets + test_ftplib + test_imaplib + test_poplib + test_smtplib + test_httplib + test_urllib2_localnet + test_xmlrpc + test_docxmlrpc name: Python ${{ matrix.python_ver }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} runs-on: ubuntu-24.04 diff --git a/.github/workflows/socat.yml b/.github/workflows/socat.yml index 56afc0210d8..64d1fafbcd0 100644 --- a/.github/workflows/socat.yml +++ b/.github/workflows/socat.yml @@ -56,6 +56,8 @@ jobs: expect_fail: "36,64,146,155,156,205,216,227,307,309,310,321,386,399,402,403,459,460,467,468,475,478,491,492,528,529" - socat_version: "1.8.0.3" expect_fail: "23,146,155,156,307,321,386,399,402,459,460,467,468,475,478,491,492,495,528,529" + - socat_version: "1.8.1.1" + expect_fail: "23,146,155,156,307,321,386,399,467,468,475,478,491,492,495,528,529" steps: - name: Checkout wolfSSL CI actions uses: actions/checkout@v5 diff --git a/.github/workflows/softhsm.yml b/.github/workflows/softhsm.yml index aeb60ce2271..6b0610638bf 100644 --- a/.github/workflows/softhsm.yml +++ b/.github/workflows/softhsm.yml @@ -48,7 +48,7 @@ jobs: fail-fast: false matrix: # List of releases to test - ref: [ 2.6.1 ] + ref: [ 2.6.1, 2.7.0 ] name: ${{ matrix.ref }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} runs-on: ubuntu-24.04 diff --git a/.github/workflows/tcpdump.yml b/.github/workflows/tcpdump.yml new file mode 100644 index 00000000000..accbbfcb09c --- /dev/null +++ b/.github/workflows/tcpdump.yml @@ -0,0 +1,122 @@ +name: tcpdump Tests + +# START OF COMMON SECTION +on: + push: + branches: [ 'release/**' ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read +# END OF COMMON SECTION + +jobs: + build_wolfssl: + name: Build wolfSSL + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + # Just to keep it the same as the testing target + runs-on: ubuntu-24.04 + # This should be a safe limit for the tests to run. + timeout-minutes: 4 + steps: + - name: Build wolfSSL + uses: wolfSSL/actions-build-autotools-project@v1 + with: + path: wolfssl + configure: --enable-tcpdump + install: true + check: false + + - name: tar build-dir + run: tar -zcf build-dir.tgz build-dir + + - name: Upload built lib + uses: actions/upload-artifact@v6 + with: + name: wolf-install-tcpdump + path: build-dir.tgz + retention-days: 5 + + tcpdump_check: + strategy: + fail-fast: false + matrix: + # List of releases to test + ref: [ 4.99.6 ] + name: ${{ matrix.ref }} + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + runs-on: ubuntu-24.04 + # This should be a safe limit for the tests to run. + timeout-minutes: 10 + needs: build_wolfssl + steps: + - name: Checkout wolfSSL CI actions + uses: actions/checkout@v5 + with: + sparse-checkout: .github/actions + fetch-depth: 1 + + - name: Download lib + uses: actions/download-artifact@v7 + with: + name: wolf-install-tcpdump + + - name: untar build-dir + run: tar -xf build-dir.tgz + + - name: Install dependencies + uses: ./.github/actions/install-apt-deps + with: + packages: bison flex + ghcr-debs-tag: ubuntu-24.04-full + + - name: Checkout OSP + uses: actions/checkout@v5 + with: + repository: wolfssl/osp + path: osp + fetch-depth: 1 + + # Ubuntu 24.04 ships libpcap 1.10.4, which reads pcap timestamps as + # signed 32-bit values. That breaks the time_2038_overflow, time_2039, + # time_2106 and time_2106_max tests. Build libpcap 1.10.5 next to + # tcpdump instead; tcpdump's configure picks up ../libpcap on its own. + - name: Checkout libpcap + uses: actions/checkout@v5 + with: + repository: the-tcpdump-group/libpcap + path: libpcap + ref: libpcap-1.10.5 + fetch-depth: 1 + + - name: Build libpcap + working-directory: libpcap + run: | + ./autogen.sh + ./configure + make -j + + - name: Checkout tcpdump + uses: actions/checkout@v5 + with: + repository: the-tcpdump-group/tcpdump + path: tcpdump + ref: tcpdump-${{ matrix.ref }} + fetch-depth: 1 + + - name: Build and test tcpdump + working-directory: tcpdump + run: | + export LD_LIBRARY_PATH=$GITHUB_WORKSPACE/build-dir/lib:$LD_LIBRARY_PATH + patch -p1 < $GITHUB_WORKSPACE/osp/tcpdump/${{ matrix.ref }}/tcpdump-${{ matrix.ref }}.patch + autoreconf -ivf + ./configure --with-wolfssl=$GITHUB_WORKSPACE/build-dir + make -j + ldd ./tcpdump | grep wolfssl + make check diff --git a/configure.ac b/configure.ac index 0028f10ba0b..e79317f56b8 100644 --- a/configure.ac +++ b/configure.ac @@ -9055,7 +9055,7 @@ if test "x$ENABLED_NGINX" = "xyes" || test "x$ENABLED_HAPROXY" = "xyes" || \ test "x$ENABLED_OPENVPN" = "xyes" || test "x$ENABLED_WPAS" != "xno" || \ test "x$ENABLED_LIGHTY" = "xyes" || test "x$ENABLED_NETSNMP" = "xyes" || \ test "x$ENABLED_KRB" = "xyes" || test "x$ENABLED_STRONGSWAN" = "xyes" || \ - test "x$ENABLED_MOSQUITTO" = "xyes" + test "x$ENABLED_MOSQUITTO" = "xyes" || test "x$ENABLED_OPENLDAP" = "xyes" then ENABLED_CRL=yes fi @@ -10557,6 +10557,11 @@ then then ENABLED_DES3="yes" fi + + if test "x$ENABLED_MD5" = "xno" + then + ENABLED_MD5="yes" + fi fi # sblim-sfcb support diff --git a/src/internal.c b/src/internal.c index 2f00929b753..2421ee15890 100644 --- a/src/internal.c +++ b/src/internal.c @@ -31837,6 +31837,25 @@ static void RemoveExcludedSuites(byte* suites, int* idx, int anon, int enull) } *idx = out; } + +#ifdef HAVE_ANON +/* Return 1 when suites contains an anonymous (no peer authentication) + * cipher suite. */ +int SuitesHaveAnon(const Suites* suites) +{ + int i; + + if (suites == NULL) + return 0; + for (i = 0; (i + 1) < suites->suiteSz; i += 2) { + if (CipherSuiteExcluded(suites->suites[i], suites->suites[i + 1], + 1, 0)) { + return 1; + } + } + return 0; +} +#endif /* HAVE_ANON */ #endif /* OPENSSL_EXTRA || OPENSSL_ALL */ /** @@ -32027,8 +32046,11 @@ static int ParseCipherList(Suites* suites, } if (XSTRCMP(name, "aNULL") == 0) { - if (allowing) + if (allowing) { haveSig |= SIG_ANON; + /* Anonymous suites are DH_anon; InitSuites needs DH too. */ + haveDH = 1; + } else haveSig &= ~SIG_ANON; /* Track exclusion (sticky) so an explicit ADH suite is dropped at diff --git a/src/pk.c b/src/pk.c index c86f4b4c107..1e64caa073c 100644 --- a/src/pk.c +++ b/src/pk.c @@ -2901,8 +2901,8 @@ WOLFSSL_DH* wolfSSL_DH_dup(WOLFSSL_DH* dh) WOLFSSL_ENTER("wolfSSL_DH_dup"); - /* Validate parameters. */ - if (dh == NULL) { + /* Validate parameters. Duplicating needs the full parameter set. */ + if ((dh == NULL) || (dh->p == NULL) || (dh->g == NULL)) { WOLFSSL_ERROR_MSG("Bad parameter"); err = 1; } @@ -3672,7 +3672,7 @@ int wolfSSL_i2d_DHparams(const WOLFSSL_DH *dh, unsigned char **out) int err = 0; /* Validate parameters. */ - if (dh == NULL) { + if ((dh == NULL) || (dh->p == NULL) || (dh->g == NULL)) { WOLFSSL_ERROR_MSG("Bad parameters"); err = 1; } @@ -3716,7 +3716,7 @@ int wolfSSL_i2d_DHparams(const WOLFSSL_DH *dh, unsigned char **out) WOLFSSL_ENTER("wolfSSL_i2d_DHparams"); /* Validate parameters. */ - if (dh == NULL) { + if ((dh == NULL) || (dh->p == NULL) || (dh->g == NULL)) { WOLFSSL_ERROR_MSG("Bad parameters"); len = 0; } @@ -3993,8 +3993,13 @@ static int wolfssl_dhparams_to_der(WOLFSSL_DH* dh, unsigned char** out, (void)heap; + /* Validate parameters. */ + if ((dh->p == NULL) || (dh->g == NULL)) { + WOLFSSL_ERROR_MSG("Bad parameters"); + err = 1; + } /* Set internal parameters based on external parameters. */ - if ((dh->inSet == 0) && (SetDhInternal(dh) != 1)) { + if ((!err) && (dh->inSet == 0) && (SetDhInternal(dh) != 1)) { WOLFSSL_ERROR_MSG("Unable to set internal DH structure"); err = 1; } @@ -4197,8 +4202,8 @@ int SetDhInternal(WOLFSSL_DH* dh) WOLFSSL_ENTER("SetDhInternal"); - /* Validate parameters. */ - if ((dh == NULL) || (dh->p == NULL) || (dh->g == NULL)) { + /* Validate parameters. g is optional: key agreement only needs p. */ + if ((dh == NULL) || (dh->p == NULL)) { WOLFSSL_ERROR_MSG("Bad function arguments"); ret = WOLFSSL_FATAL_ERROR; } @@ -4218,8 +4223,8 @@ int SetDhInternal(WOLFSSL_DH* dh) ret = WOLFSSL_FATAL_ERROR; } } - if (ret == 1) { - /* Transfer generator. */ + /* Transfer generator if available. */ + if ((ret == 1) && (dh->g != NULL)) { if (wolfssl_bn_get_value(dh->g, &key->g) != 1) { ret = WOLFSSL_FATAL_ERROR; } @@ -4250,8 +4255,10 @@ int SetDhInternal(WOLFSSL_DH* dh) #endif /* WOLFSSL_DH_EXTRA */ if (ret == 1) { - /* On success record that the internal values have been set. */ - dh->inSet = 1; + /* Record that the internal values have been set. Without a generator + * the internal key is incomplete, so keep it unset to force a + * re-sync when the generator is added later. */ + dh->inSet = (dh->g != NULL); } return ret; diff --git a/src/ssl.c b/src/ssl.c index 894fcf026e5..c3636f56b67 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -3099,7 +3099,7 @@ static int CheckcipherList(const char* list) * * returns WOLFSSL_SUCCESS on success and sets the cipher suite list */ -static int wolfSSL_parse_cipher_list(WOLFSSL_CTX* ctx, WOLFSSL* ssl, +static int wolfSSL_parse_cipher_list_ex(WOLFSSL_CTX* ctx, WOLFSSL* ssl, Suites* suites, const char* list) { int ret = 0; @@ -3227,6 +3227,25 @@ static int wolfSSL_parse_cipher_list(WOLFSSL_CTX* ctx, WOLFSSL* ssl, return ret; } +static int wolfSSL_parse_cipher_list(WOLFSSL_CTX* ctx, WOLFSSL* ssl, + Suites* suites, const char* list) +{ + int ret = wolfSSL_parse_cipher_list_ex(ctx, ssl, suites, list); + +#ifdef HAVE_ANON + /* Like OpenSSL, a list with anonymous suites lets a server run without + * a certificate. */ + if (ret == WOLFSSL_SUCCESS && SuitesHaveAnon(suites)) { + if (ctx != NULL) + ctx->useAnon = 1; + else if (ssl != NULL) + ssl->options.useAnon = 1; + } +#endif + + return ret; +} + #endif diff --git a/src/ssl_asn1.c b/src/ssl_asn1.c index 29f35fb6be5..070524dc20b 100644 --- a/src/ssl_asn1.c +++ b/src/ssl_asn1.c @@ -3895,23 +3895,28 @@ void wolfSSL_ASN1_TIME_free(WOLFSSL_ASN1_TIME* t) XFREE(t, NULL, DYNAMIC_TYPE_OPENSSL); } -#ifndef NO_WOLFSSL_STUB +#if !defined(NO_ASN_TIME) && !defined(USER_TIME) && !defined(TIME_OVERRIDES) /* Set the Unix time GMT into ASN.1 TIME object. * - * Not implemented. - * - * @param [in, out] a ASN.1 TIME object. + * @param [in, out] a ASN.1 TIME object. Allocated when NULL. * @param [in] t Unix time GMT. - * @return An ASN.1 TIME object. + * @return An ASN.1 TIME object on success. + * @return NULL on failure. */ WOLFSSL_ASN1_TIME *wolfSSL_ASN1_TIME_set(WOLFSSL_ASN1_TIME *a, time_t t) +{ + WOLFSSL_ENTER("wolfSSL_ASN1_TIME_set"); + return wolfSSL_ASN1_TIME_adj(a, t, 0, 0); +} +#elif !defined(NO_WOLFSSL_STUB) +WOLFSSL_ASN1_TIME *wolfSSL_ASN1_TIME_set(WOLFSSL_ASN1_TIME *a, time_t t) { WOLFSSL_STUB("wolfSSL_ASN1_TIME_set"); (void)a; (void)t; return a; } -#endif /* !NO_WOLFSSL_STUB */ +#endif /* !NO_ASN_TIME && !USER_TIME && !TIME_OVERRIDES */ #ifndef NO_ASN_TIME /* Convert time to Unix time (GMT). diff --git a/src/ssl_bn.c b/src/ssl_bn.c index ea1355cb4b3..f74c42a4704 100644 --- a/src/ssl_bn.c +++ b/src/ssl_bn.c @@ -880,24 +880,38 @@ int wolfSSL_BN_is_odd(const WOLFSSL_BIGNUM* bn) return ret; } -#ifndef NO_WOLFSSL_STUB -/* Mask the lowest n bits. - * - * TODO: mp_mod_2d() +#ifndef WOLFSSL_SP_MATH +/* Keep only the lowest n bits. bn = bn mod 2^n * * Return compliant with OpenSSL. * * @param [in, out] bn Big number to operation on. * @param [in] n Number of bits. + * @return 1 on success. + * @return 0 when bn or internal representation of bn is NULL. + * @return 0 when n is negative. * @return 0 on failure. */ int wolfSSL_mask_bits(WOLFSSL_BIGNUM* bn, int n) { - (void)bn; - (void)n; + int ret = 1; + WOLFSSL_ENTER("wolfSSL_BN_mask_bits"); - WOLFSSL_STUB("BN_mask_bits"); - return 0; + + /* Validate parameters. */ + if (BN_IS_NULL(bn) || (n < 0)) { + WOLFSSL_MSG("bn NULL error"); + ret = 0; + } + + /* Use wolfCrypt perform operation. */ + if ((ret == 1) && (mp_mod_2d((mp_int*)bn->internal, n, + (mp_int*)bn->internal) != MP_OKAY)) { + WOLFSSL_MSG("mp_mod_2d error"); + ret = 0; + } + + return ret; } #endif diff --git a/src/ssl_err.c b/src/ssl_err.c index 27100bf7c7b..eab59398aab 100644 --- a/src/ssl_err.c +++ b/src/ssl_err.c @@ -693,6 +693,37 @@ void wolfSSL_ERR_remove_state(unsigned long id) WOLFSSL_MSG("Error with removing the state"); } } + +/* Mark the newest entry in the error queue. + * + * @return 1 on success. + * @return 0 when the queue is empty or there is no error queue. + */ +int wolfSSL_ERR_set_mark(void) +{ + WOLFSSL_ENTER("wolfSSL_ERR_set_mark"); +#ifdef WOLFSSL_HAVE_ERROR_QUEUE + return wc_SetErrorMark(); +#else + return 0; +#endif +} + +/* Remove entries newer than the last mark and clear that mark. + * + * @return 1 when a mark was found. + * @return 0 when no mark was found and the queue is now empty, or there is + * no error queue. + */ +int wolfSSL_ERR_pop_to_mark(void) +{ + WOLFSSL_ENTER("wolfSSL_ERR_pop_to_mark"); +#ifdef WOLFSSL_HAVE_ERROR_QUEUE + return wc_PopErrorMark(); +#else + return 0; +#endif +} #endif #endif /* !WOLFCRYPT_ONLY */ diff --git a/src/x509.c b/src/x509.c index 514c474bd0f..c989d1bbe4c 100644 --- a/src/x509.c +++ b/src/x509.c @@ -3239,6 +3239,124 @@ void wolfSSL_X509V3_set_ctx_nodb(WOLFSSL_X509V3_CTX* ctx) #endif /* !NO_WOLFSSL_STUB */ #ifdef OPENSSL_EXTRA +static const char* wolfssl_x509v3_skip_ws(const char* s) +{ + while (*s == ' ' || *s == '\t') + s++; + return s; +} + +/* Check for a leading "critical," in an extension value. Whitespace is + * allowed around the keyword and the comma. Advances value past the prefix + * and any leading whitespace. + * + * @return 1 when the value is critical. + * @return 0 otherwise. + */ +static int wolfssl_x509v3_check_critical(const char** value) +{ + const char* s = wolfssl_x509v3_skip_ws(*value); + int crit = 0; + + if (XSTRNCMP(s, "critical", 8) == 0) { + const char* p = wolfssl_x509v3_skip_ws(s + 8); + if (*p == ',') { + s = wolfssl_x509v3_skip_ws(p + 1); + crit = 1; + } + } + + *value = s; + return crit; +} + +/* Set the basicConstraints extension value from an OpenSSL style string. + * Format: "CA:TRUE|FALSE[,pathlen:N]". Spaces and tabs around a token are + * ignored. CA is required. Each token may appear only once. + * + * @return WOLFSSL_SUCCESS on success. + * @return WOLFSSL_FAILURE on bad value or allocation error. + */ +static int wolfssl_ext_bc_from_str(WOLFSSL_X509_EXTENSION* ext, + const char* value) +{ + const char* s = value; + int isCa = 0; + int caSet = 0; + int pathLen = 0; + int pathLenSet = 0; + + ext->obj = wolfSSL_OBJ_nid2obj(WC_NID_basic_constraints); + if (ext->obj == NULL) { + WOLFSSL_MSG("wolfSSL_OBJ_nid2obj failed"); + return WOLFSSL_FAILURE; + } + + for (;;) { + const char* tok; + const char* end; + const char* next; + size_t len; + + tok = wolfssl_x509v3_skip_ws(s); + end = tok; + while ((*end != '\0') && (*end != ',')) + end++; + next = end; + while ((end > tok) && ((end[-1] == ' ') || (end[-1] == '\t'))) + end--; + len = (size_t)(end - tok); + + if ((len == 7) && (XSTRNCASECMP(tok, "CA:TRUE", 7) == 0)) { + if (caSet) + return WOLFSSL_FAILURE; + isCa = 1; + caSet = 1; + } + else if ((len == 8) && (XSTRNCASECMP(tok, "CA:FALSE", 8) == 0)) { + if (caSet) + return WOLFSSL_FAILURE; + caSet = 1; + } + else if ((len > 8) && (XSTRNCASECMP(tok, "pathlen:", 8) == 0)) { + const char* num; + + if (pathLenSet) + return WOLFSSL_FAILURE; + for (num = tok + 8; num < end; num++) { + if ((*num < '0') || (*num > '9')) + return WOLFSSL_FAILURE; + pathLen = (pathLen * 10) + (*num - '0'); + if (pathLen > WOLFSSL_MAX_PATH_LEN) + return WOLFSSL_FAILURE; + } + pathLenSet = 1; + } + else { + return WOLFSSL_FAILURE; + } + + if (*next != ',') + break; + s = next + 1; + } + + if (!caSet) { + WOLFSSL_MSG("basicConstraints value missing CA"); + return WOLFSSL_FAILURE; + } + + ext->obj->ca = isCa; + if (pathLenSet) { + ext->obj->pathlen = wolfSSL_ASN1_INTEGER_new(); + if (ext->obj->pathlen == NULL) + return WOLFSSL_FAILURE; + ext->obj->pathlen->length = pathLen; + } + + return WOLFSSL_SUCCESS; +} + static WOLFSSL_X509_EXTENSION* createExtFromStr(int nid, const char *value) { WOLFSSL_X509_EXTENSION* ext; @@ -3249,6 +3367,7 @@ static WOLFSSL_X509_EXTENSION* createExtFromStr(int nid, const char *value) return NULL; } ext->value.nid = nid; + ext->crit = wolfssl_x509v3_check_critical(&value); switch (nid) { case WC_NID_subject_key_identifier: @@ -3315,6 +3434,12 @@ static WOLFSSL_X509_EXTENSION* createExtFromStr(int nid, const char *value) } ext->value.type = EXT_KEY_USAGE_OID; break; + case WC_NID_basic_constraints: + if (wolfssl_ext_bc_from_str(ext, value) != WOLFSSL_SUCCESS) { + WOLFSSL_MSG("wolfssl_ext_bc_from_str error"); + goto err_cleanup; + } + break; default: WOLFSSL_MSG("invalid or unsupported NID"); goto err_cleanup; @@ -3814,6 +3939,11 @@ int wolfSSL_X509_pubkey_digest(const WOLFSSL_X509 *x509, const WOLFSSL_EVP_MD *digest, unsigned char* buf, unsigned int* len) { int ret; + const byte* key; + word32 sz; + word32 idx = 0; + int keySz; + int len2 = 0; WOLFSSL_ENTER("wolfSSL_X509_pubkey_digest"); @@ -3827,8 +3957,23 @@ int wolfSSL_X509_pubkey_digest(const WOLFSSL_X509 *x509, return WOLFSSL_FAILURE; } - ret = wolfSSL_EVP_Digest(x509->pubKey.buffer, x509->pubKey.length, buf, - len, digest, NULL); + key = x509->pubKey.buffer; + sz = x509->pubKey.length; + keySz = (int)sz; + + /* OpenSSL digests the subjectPublicKey. Decoded certificates keep the + * key alone but wolfSSL_X509_set_pubkey stores a SubjectPublicKeyInfo, + * so step over that wrapper when it is present. */ + if ((GetSequence(key, &idx, &len2, sz) >= 0) && + (GetSequence(key, &idx, &len2, sz) >= 0)) { + idx += (word32)len2; + if (CheckBitString(key, &idx, &len2, sz, 1, NULL) >= 0) { + key += idx; + keySz = len2; + } + } + + ret = wolfSSL_EVP_Digest(key, keySz, buf, len, digest, NULL); WOLFSSL_LEAVE("wolfSSL_X509_pubkey_digest", ret); return ret; } @@ -4921,14 +5066,13 @@ void wolfSSL_sk_ACCESS_DESCRIPTION_free(WOLFSSL_STACK* sk) } -/* AUTHORITY_INFO_ACCESS object is a stack of ACCESS_DESCRIPTION objects, - * to free the stack the WOLFSSL_ACCESS_DESCRIPTION stack free function is - * used */ +/* AUTHORITY_INFO_ACCESS object is a stack of ACCESS_DESCRIPTION objects. + * Free the entries and the stack, as OpenSSL does. */ void wolfSSL_AUTHORITY_INFO_ACCESS_free( WOLF_STACK_OF(WOLFSSL_ACCESS_DESCRIPTION)* sk) { WOLFSSL_ENTER("wolfSSL_AUTHORITY_INFO_ACCESS_free"); - wolfSSL_sk_ACCESS_DESCRIPTION_free(sk); + wolfSSL_sk_ACCESS_DESCRIPTION_pop_free(sk, wolfSSL_ACCESS_DESCRIPTION_free); } void wolfSSL_AUTHORITY_INFO_ACCESS_pop_free( @@ -6450,6 +6594,7 @@ WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509) } XMEMCPY(key->pkey.ptr, x509->pubKey.buffer, x509->pubKey.length); key->pkey_sz = (int)x509->pubKey.length; + key->isPriv = 0; #ifdef HAVE_ECC key->pkey_curve = (int)x509->pkCurveOID; @@ -10751,6 +10896,15 @@ void wolfSSL_X509_VERIFY_PARAM_set_hostflags(WOLFSSL_X509_VERIFY_PARAM* param, } } +unsigned int wolfSSL_X509_VERIFY_PARAM_get_hostflags( + const WOLFSSL_X509_VERIFY_PARAM* param) +{ + if (param == NULL) { + return 0; + } + return param->hostFlags; +} + /* Sets the expected IP address to ipasc. * * param is a pointer to the X509_VERIFY_PARAM structure @@ -17092,54 +17246,24 @@ void wolfSSL_X509V3_set_ctx(WOLFSSL_X509V3_CTX* ctx, WOLFSSL_X509* issuer, WOLFSSL_X509* subject, WOLFSSL_X509* req, WOLFSSL_X509_CRL* crl, int flag) { - int ret = WOLFSSL_SUCCESS; WOLFSSL_ENTER("wolfSSL_X509V3_set_ctx"); - if (!ctx) { - ret = WOLFSSL_FAILURE; + if (ctx == NULL) { WOLFSSL_MSG("wolfSSL_X509V3_set_ctx() called with null ctx."); + return; } - if (ret == WOLFSSL_SUCCESS && (ctx->x509 != NULL)) { - ret = WOLFSSL_FAILURE; - WOLFSSL_MSG("wolfSSL_X509V3_set_ctx() called " - "with ctx->x509 already allocated."); - } - - if (ret == WOLFSSL_SUCCESS) { - ctx->x509 = wolfSSL_X509_new_ex( - (issuer && issuer->heap) ? issuer->heap : - (subject && subject->heap) ? subject->heap : - (req && req->heap) ? req->heap : - NULL); - if (!ctx->x509) { - ret = WOLFSSL_FAILURE; - WOLFSSL_MSG("wolfSSL_X509_new_ex() failed " - "in wolfSSL_X509V3_set_ctx()."); - } - } - - /* Set parameters in ctx as long as ret == WOLFSSL_SUCCESS */ - if (ret == WOLFSSL_SUCCESS && issuer) - ret = wolfSSL_X509_set_issuer_name(ctx->x509, &issuer->issuer); + ctx->issuer = issuer; + ctx->subject = subject; - if (ret == WOLFSSL_SUCCESS && subject) - ret = wolfSSL_X509_set_subject_name(ctx->x509, &subject->subject); - - if (ret == WOLFSSL_SUCCESS && req) { + if (req != NULL) { WOLFSSL_MSG("req not implemented."); } - - if (ret == WOLFSSL_SUCCESS && crl) { + if (crl != NULL) { WOLFSSL_MSG("crl not implemented."); } - - if (ret == WOLFSSL_SUCCESS && flag) { + if (flag != 0) { WOLFSSL_MSG("flag not implemented."); } - - if (ret != WOLFSSL_SUCCESS) { - WOLFSSL_MSG("Error setting WOLFSSL_X509V3_CTX parameters."); - } } #ifndef NO_BIO diff --git a/tests/api.c b/tests/api.c index 777336230c8..4d5942fbb04 100644 --- a/tests/api.c +++ b/tests/api.c @@ -2851,6 +2851,122 @@ static int test_wolfSSL_set_cipher_list_exclusions(void) #endif /* TEST_CIPHER_EXCLUDE_ANON || TEST_CIPHER_EXCLUDE_NULL */ return EXPECT_RESULT(); } + +/* OpenSSL "aNULL" selects the anonymous suites; a server that only lists + * them has no certificate. Check the keyword generates the ADH suites and + * that both ends handshake without any certificate over TLS 1.2. */ +#if defined(OPENSSL_EXTRA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) && \ + defined(BUILD_TLS_DH_anon_WITH_AES_128_CBC_SHA) && \ + !defined(WOLFSSL_SESSION_EXPORT) + #define TEST_CIPHER_LIST_ANON +#endif + +#ifdef TEST_CIPHER_LIST_ANON +static int test_cipher_list_anon_ctx_ready(WOLFSSL_CTX* ctx) +{ + EXPECT_DECLS; + wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(wolfSSL_CTX_set_cipher_list(ctx, "aNULL"), WOLFSSL_SUCCESS); + return EXPECT_RESULT(); +} + +static int test_cipher_list_anon_on_result(WOLFSSL* ssl) +{ + EXPECT_DECLS; + ExpectStrEQ(wolfSSL_get_cipher_name(ssl), "ADH-AES128-SHA"); + return EXPECT_RESULT(); +} +#endif + +static int test_wolfSSL_set_cipher_list_anon(void) +{ + EXPECT_DECLS; +#ifdef TEST_CIPHER_LIST_ANON + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + test_ssl_cbf client_cbf; + test_ssl_cbf server_cbf; + + /* OPENSSL_COMPATIBLE_DEFAULTS already allows anon on every CTX, so clear + * the flag first: the checks below must see the cipher list set it. */ + + /* "aNULL" on the CTX allows anon there and on SSLs made from it. A + * certificate-less server CTX can only make an SSL once anon is on. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_server_method())); + if (ctx != NULL) + ctx->useAnon = 0; + ExpectIntEQ(wolfSSL_CTX_set_cipher_list(ctx, "aNULL"), WOLFSSL_SUCCESS); + ExpectIntEQ(ctx->useAnon, 1); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(ssl->options.useAnon, 1); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* "aNULL" alone must generate the anonymous suites. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + if (ctx != NULL) + ctx->useAnon = 0; + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) + ssl->options.useAnon = 0; + ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "aNULL"), WOLFSSL_SUCCESS); + ExpectIntEQ(test_suites_contains(ssl, CIPHER_BYTE, + TLS_DH_anon_WITH_AES_128_CBC_SHA), 1); + ExpectIntEQ(ssl->options.useAnon, 1); + wolfSSL_free(ssl); + ssl = NULL; + + /* An explicit anonymous suite also allows anon on the SSL. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) + ssl->options.useAnon = 0; + ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "ADH-AES128-SHA"), + WOLFSSL_SUCCESS); + ExpectIntEQ(ssl->options.useAnon, 1); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* A list without anonymous suites must not turn anon on. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + if (ctx != NULL) + ctx->useAnon = 0; + ExpectIntEQ(wolfSSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL"), + WOLFSSL_SUCCESS); + ExpectIntEQ(ctx->useAnon, 0); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(ssl->options.useAnon, 0); + ExpectIntEQ(test_suites_contains(ssl, CIPHER_BYTE, + TLS_DH_anon_WITH_AES_128_CBC_SHA), 0); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* Handshake with no certificates on either side. */ + XMEMSET(&client_cbf, 0, sizeof(client_cbf)); + XMEMSET(&server_cbf, 0, sizeof(server_cbf)); + client_cbf.method = wolfTLSv1_2_client_method; + server_cbf.method = wolfTLSv1_2_server_method; + client_cbf.caPemFile = ""; + client_cbf.certPemFile = ""; + client_cbf.keyPemFile = ""; + server_cbf.caPemFile = ""; + server_cbf.certPemFile = ""; + server_cbf.keyPemFile = ""; + client_cbf.ctx_ready = test_cipher_list_anon_ctx_ready; + server_cbf.ctx_ready = test_cipher_list_anon_ctx_ready; + client_cbf.on_result = test_cipher_list_anon_on_result; + server_cbf.on_result = test_cipher_list_anon_on_result; + ExpectIntEQ(test_wolfSSL_client_server_nofail_memio(&client_cbf, + &server_cbf, NULL), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} #ifdef TEST_CIPHER_EXCLUDE_ANON #undef TEST_CIPHER_EXCLUDE_ANON #endif @@ -14289,7 +14405,12 @@ static int test_wolfSSL_certs(void) /************* Get Digest of Certificate ******************/ { byte digest[64]; /* max digest size */ + byte digest2[64]; word32 digestSz; + byte keyId[WC_SHA_DIGEST_SIZE]; + int keyIdSz = (int)sizeof(keyId); + EVP_PKEY* pubKey = NULL; + X509* x509Pub = NULL; X509* x509Empty = NULL; XMEMSET(digest, 0, sizeof(digest)); @@ -14303,6 +14424,20 @@ static int test_wolfSSL_certs(void) NULL), WOLFSSL_SUCCESS); ExpectIntEQ(X509_pubkey_digest(x509ext, wolfSSL_EVP_sha1(), digest, &digestSz), WOLFSSL_SUCCESS); + /* SHA-1 of the subjectPublicKey is the subject key identifier. */ + ExpectIntEQ((int)digestSz, WC_SHA_DIGEST_SIZE); + ExpectNotNull(wolfSSL_X509_get_subjectKeyID(x509ext, keyId, &keyIdSz)); + ExpectIntEQ(keyIdSz, WC_SHA_DIGEST_SIZE); + ExpectIntEQ(XMEMCMP(digest, keyId, WC_SHA_DIGEST_SIZE), 0); + /* A key stored with X509_set_pubkey digests to the same value. */ + ExpectNotNull(pubKey = X509_get_pubkey(x509ext)); + ExpectNotNull(x509Pub = wolfSSL_X509_new()); + ExpectIntEQ(X509_set_pubkey(x509Pub, pubKey), WOLFSSL_SUCCESS); + ExpectIntEQ(X509_pubkey_digest(x509Pub, wolfSSL_EVP_sha1(), digest2, + &digestSz), WOLFSSL_SUCCESS); + ExpectIntEQ(XMEMCMP(digest2, keyId, WC_SHA_DIGEST_SIZE), 0); + wolfSSL_X509_free(x509Pub); + EVP_PKEY_free(pubKey); ExpectIntEQ(X509_pubkey_digest(x509ext, wolfSSL_EVP_sha256(), digest, &digestSz), WOLFSSL_SUCCESS); @@ -20688,6 +20823,60 @@ static int test_wolfSSL_ERR_get_error_order(void) return EXPECT_RESULT(); } +static int test_wolfSSL_ERR_set_mark(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_HAVE_ERROR_QUEUE) && defined(OPENSSL_EXTRA) + wolfSSL_ERR_clear_error(); + + /* Empty queue: nothing to mark, nothing to pop. */ + ExpectIntEQ(wolfSSL_ERR_set_mark(), 0); + ExpectIntEQ(wolfSSL_ERR_pop_to_mark(), 0); + ExpectIntEQ(wolfSSL_ERR_get_error(), 0); + + /* Pop removes entries newer than the mark and keeps the marked one. */ + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_NO_SIGNER_E), "test", 0); + ExpectIntEQ(wolfSSL_ERR_set_mark(), 1); + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_SELF_SIGNED_E), "test", 0); + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_PARSE_E), "test", 0); + ExpectIntEQ(wolfSSL_ERR_pop_to_mark(), 1); + ExpectIntEQ(wolfSSL_ERR_peek_last_error(), + -WC_NO_ERR_TRACE(ASN_NO_SIGNER_E)); + ExpectIntEQ(wolfSSL_ERR_get_error(), -WC_NO_ERR_TRACE(ASN_NO_SIGNER_E)); + ExpectIntEQ(wolfSSL_ERR_get_error(), 0); + + /* Mark is cleared by the pop, so a second pop empties the queue. */ + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_NO_SIGNER_E), "test", 0); + ExpectIntEQ(wolfSSL_ERR_set_mark(), 1); + ExpectIntEQ(wolfSSL_ERR_pop_to_mark(), 1); + ExpectIntEQ(wolfSSL_ERR_peek_error(), -WC_NO_ERR_TRACE(ASN_NO_SIGNER_E)); + ExpectIntEQ(wolfSSL_ERR_pop_to_mark(), 0); + ExpectIntEQ(wolfSSL_ERR_get_error(), 0); + + /* No mark: pop empties the queue. */ + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_NO_SIGNER_E), "test", 0); + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_SELF_SIGNED_E), "test", 0); + ExpectIntEQ(wolfSSL_ERR_pop_to_mark(), 0); + ExpectIntEQ(wolfSSL_ERR_get_error(), 0); + + /* Nested marks. */ + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_NO_SIGNER_E), "test", 0); + ExpectIntEQ(wolfSSL_ERR_set_mark(), 1); + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_SELF_SIGNED_E), "test", 0); + ExpectIntEQ(wolfSSL_ERR_set_mark(), 1); + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_PARSE_E), "test", 0); + ExpectIntEQ(wolfSSL_ERR_pop_to_mark(), 1); + ExpectIntEQ(wolfSSL_ERR_peek_last_error(), + -WC_NO_ERR_TRACE(ASN_SELF_SIGNED_E)); + ExpectIntEQ(wolfSSL_ERR_pop_to_mark(), 1); + ExpectIntEQ(wolfSSL_ERR_peek_last_error(), + -WC_NO_ERR_TRACE(ASN_NO_SIGNER_E)); + ExpectIntEQ(wolfSSL_ERR_get_error(), -WC_NO_ERR_TRACE(ASN_NO_SIGNER_E)); + ExpectIntEQ(wolfSSL_ERR_get_error(), 0); +#endif /* WOLFSSL_HAVE_ERROR_QUEUE && OPENSSL_EXTRA */ + return EXPECT_RESULT(); +} + static int test_wolfSSL_ERR_GET_REASON_version_mismatch(void) { EXPECT_DECLS; @@ -41819,6 +42008,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_error_queue_per_thread), TEST_DECL(test_wolfSSL_ERR_put_error), TEST_DECL(test_wolfSSL_ERR_get_error_order), + TEST_DECL(test_wolfSSL_ERR_set_mark), TEST_DECL(test_wolfSSL_ERR_GET_REASON_version_mismatch), #ifndef NO_BIO TEST_DECL(test_wolfSSL_ERR_print_errors), @@ -42013,6 +42203,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_set_cipher_list_tls12_with_version), TEST_DECL(test_wolfSSL_set_cipher_list_tls13_with_version), TEST_DECL(test_wolfSSL_set_cipher_list_exclusions), + TEST_DECL(test_wolfSSL_set_cipher_list_anon), TEST_DECL(test_wolfSSL_set_alpn_protos_default_fails), TEST_DECL(test_wolfSSL_CTX_use_certificate), TEST_DECL(test_wolfSSL_CTX_use_certificate_file), diff --git a/tests/api/test_evp_cipher.c b/tests/api/test_evp_cipher.c index c3de8b18b23..eb08b32c23c 100644 --- a/tests/api/test_evp_cipher.c +++ b/tests/api/test_evp_cipher.c @@ -639,6 +639,75 @@ int test_wolfSSL_EVP_CipherUpdate_Null(void) return EXPECT_RESULT(); } +/* Decrypting with padding in parts must never write more than + * inl + block_size bytes into out. */ +int test_wolfSSL_EVP_DecryptUpdate_partial(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_AES) && defined(HAVE_AES_CBC) && \ + defined(WOLFSSL_AES_128) + WOLFSSL_EVP_CIPHER_CTX* ctx = NULL; + const byte key[16] = { 0 }; + const byte iv[16] = { 0 }; + byte plain[70]; + byte cipher[80]; + byte dec[96]; + byte out[96]; + int cipherLen = 0; + int decLen = 0; + int outl = 0; + int i; + /* Part sizes that leave a held back block next to buffered bytes. */ + const int parts[] = { 3, 16, 3, 29, 16, 8, 5 }; + int off = 0; + + for (i = 0; i < (int)sizeof(plain); i++) + plain[i] = (byte)i; + + ExpectNotNull(ctx = wolfSSL_EVP_CIPHER_CTX_new()); + ExpectIntEQ(wolfSSL_EVP_CipherInit_ex(ctx, wolfSSL_EVP_aes_128_cbc(), + NULL, key, iv, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_EVP_CipherUpdate(ctx, cipher, &outl, plain, + (int)sizeof(plain)), WOLFSSL_SUCCESS); + cipherLen = outl; + ExpectIntEQ(wolfSSL_EVP_CipherFinal(ctx, cipher + cipherLen, &outl), + WOLFSSL_SUCCESS); + cipherLen += outl; + ExpectIntEQ(cipherLen, 80); + + ExpectIntEQ(wolfSSL_EVP_CipherInit_ex(ctx, wolfSSL_EVP_aes_128_cbc(), + NULL, key, iv, 0), WOLFSSL_SUCCESS); + for (i = 0; i < (int)(sizeof(parts) / sizeof(parts[0])); i++) { + int inl = parts[i]; + int j; + + XMEMSET(out, 0xAA, sizeof(out)); + ExpectIntEQ(wolfSSL_EVP_CipherUpdate(ctx, out, &outl, cipher + off, + inl), WOLFSSL_SUCCESS); + ExpectIntLE(outl, inl + 16); + /* Nothing written past inl + block_size. */ + for (j = inl + 16; j < (int)sizeof(out); j++) + ExpectIntEQ(out[j], 0xAA); + ExpectIntLE(decLen + outl, (int)sizeof(dec)); + if (EXPECT_SUCCESS()) + XMEMCPY(dec + decLen, out, (size_t)outl); + decLen += outl; + off += inl; + } + ExpectIntEQ(off, cipherLen); + ExpectIntEQ(wolfSSL_EVP_CipherFinal(ctx, out, &outl), WOLFSSL_SUCCESS); + ExpectIntLE(decLen + outl, (int)sizeof(dec)); + if (EXPECT_SUCCESS()) + XMEMCPY(dec + decLen, out, (size_t)outl); + decLen += outl; + ExpectIntEQ(decLen, (int)sizeof(plain)); + ExpectBufEQ(dec, plain, sizeof(plain)); + + wolfSSL_EVP_CIPHER_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + /* Test for wolfSSL_EVP_CIPHER_type_string() */ int test_wolfSSL_EVP_CIPHER_type_string(void) { diff --git a/tests/api/test_evp_cipher.h b/tests/api/test_evp_cipher.h index 7c8c1a0e1bd..e02fcbe4bc7 100644 --- a/tests/api/test_evp_cipher.h +++ b/tests/api/test_evp_cipher.h @@ -32,6 +32,7 @@ int test_wolfSSL_EVP_get_cipherbynid(void); int test_wolfSSL_EVP_CIPHER_block_size(void); int test_wolfSSL_EVP_CIPHER_iv_length(void); int test_wolfSSL_EVP_CipherUpdate_Null(void); +int test_wolfSSL_EVP_DecryptUpdate_partial(void); int test_wolfSSL_EVP_CIPHER_type_string(void); int test_wolfSSL_EVP_BytesToKey(void); int test_wolfSSL_EVP_Cipher_extra(void); @@ -77,6 +78,7 @@ int test_evp_cipher_aead_aad_overflow(void); TEST_DECL_GROUP("evp_cipher", test_wolfSSL_EVP_CIPHER_block_size), \ TEST_DECL_GROUP("evp_cipher", test_wolfSSL_EVP_CIPHER_iv_length), \ TEST_DECL_GROUP("evp_cipher", test_wolfSSL_EVP_CipherUpdate_Null), \ + TEST_DECL_GROUP("evp_cipher", test_wolfSSL_EVP_DecryptUpdate_partial), \ TEST_DECL_GROUP("evp_cipher", test_wolfSSL_EVP_CIPHER_type_string), \ TEST_DECL_GROUP("evp_cipher", test_wolfSSL_EVP_BytesToKey), \ TEST_DECL_GROUP("evp_cipher", test_wolfSSL_EVP_Cipher_extra), \ diff --git a/tests/api/test_evp_pkey.c b/tests/api/test_evp_pkey.c index 9414b943fda..f1666a6a2b0 100644 --- a/tests/api/test_evp_pkey.c +++ b/tests/api/test_evp_pkey.c @@ -144,6 +144,9 @@ int test_wolfSSL_EVP_PKEY_id(void) ExpectIntEQ(wolfSSL_EVP_PKEY_id(pkey), EVP_PKEY_RSA); + ExpectIntEQ(EVP_PKEY_RSA_PSS, NID_rsassaPss); + ExpectIntNE(EVP_PKEY_RSA_PSS, EVP_PKEY_RSA); + EVP_PKEY_free(pkey); #endif return EXPECT_RESULT(); @@ -508,6 +511,24 @@ int test_wolfSSL_EVP_PKEY_new_mac_key(void) ExpectIntEQ((int)checkPwSz, 0); wolfSSL_EVP_PKEY_free(key); key = NULL; + + /* EVP_PKEY_new_raw_private_key accepts HMAC keys too. */ + ExpectNotNull(key = wolfSSL_EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, + NULL, pw, (size_t)pwSz)); + ExpectIntEQ(EVP_PKEY_id(key), EVP_PKEY_HMAC); + checkPw = NULL; + checkPwSz = 0; + ExpectNotNull(checkPw = wolfSSL_EVP_PKEY_get0_hmac(key, &checkPwSz)); + ExpectIntEQ((int)checkPwSz, pwSz); + ExpectIntEQ(XMEMCMP(checkPw, pw, pwSz), 0); + wolfSSL_EVP_PKEY_free(key); + key = NULL; + + ExpectNotNull(key = wolfSSL_EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, + NULL, NULL, 0)); + ExpectIntEQ(key->pkey_sz, 0); + wolfSSL_EVP_PKEY_free(key); + key = NULL; #endif /* OPENSSL_EXTRA */ return EXPECT_RESULT(); } @@ -814,6 +835,266 @@ int test_EVP_PKEY_cmp(void) return EXPECT_RESULT(); } +#if defined(OPENSSL_EXTRA) && \ + ((!defined(NO_RSA) && defined(USE_CERT_BUFFERS_2048)) || \ + (defined(HAVE_ECC) && defined(USE_CERT_BUFFERS_256))) +/* Check dup is a distinct key with identical encoding, usable after src is + * freed. */ +static int test_EVP_PKEY_dup_check(EVP_PKEY** src, int id, int priv) +{ + EXPECT_DECLS; + EVP_PKEY* dup = NULL; + unsigned char* srcDer = NULL; + unsigned char* dupDer = NULL; + int srcSz = 0; + int dupSz = 0; + + ERR_clear_error(); + ExpectNotNull(dup = EVP_PKEY_dup(*src)); + /* A successful dup must not leave anything on the error queue. */ + ExpectIntEQ(ERR_peek_error(), 0); + ExpectPtrNE(dup, *src); + ExpectIntEQ(EVP_PKEY_id(dup), id); + if (priv) { + ExpectIntGT(srcSz = i2d_PrivateKey(*src, &srcDer), 0); + } + else { + ExpectIntGT(srcSz = i2d_PUBKEY(*src, &srcDer), 0); + } + EVP_PKEY_free(*src); + *src = NULL; + if (priv) { + ExpectIntGT(dupSz = i2d_PrivateKey(dup, &dupDer), 0); + } + else { + ExpectIntGT(dupSz = i2d_PUBKEY(dup, &dupDer), 0); + } + ExpectIntEQ(srcSz, dupSz); + ExpectIntEQ(XMEMCMP(srcDer, dupDer, (size_t)srcSz), 0); +#ifndef NO_RSA + if (id == EVP_PKEY_RSA) { + ExpectNotNull(EVP_PKEY_get0_RSA(dup)); + } +#endif +#ifdef HAVE_ECC + if (id == EVP_PKEY_EC) { + ExpectNotNull(EVP_PKEY_get0_EC_KEY(dup)); + } +#endif + XFREE(srcDer, NULL, DYNAMIC_TYPE_OPENSSL); + XFREE(dupDer, NULL, DYNAMIC_TYPE_OPENSSL); + EVP_PKEY_free(dup); + + return EXPECT_RESULT(); +} +#endif /* OPENSSL_EXTRA && (RSA 2048 or ECC 256 cert buffers) */ + +/* d2i_evp_pkey() only knows DSA and DH in these builds. */ +#if defined(OPENSSL_EXTRA) && !defined(NO_DSA) && \ + defined(USE_CERT_BUFFERS_2048) && (defined(WOLFSSL_QT) || \ + defined(OPENSSL_ALL) || defined(WOLFSSL_OPENSSH)) + #define TEST_EVP_PKEY_DUP_DSA +#endif +#if defined(OPENSSL_EXTRA) && !defined(NO_DH) && \ + defined(USE_CERT_BUFFERS_2048) && (defined(WOLFSSL_QT) || \ + defined(OPENSSL_ALL) || defined(WOLFSSL_OPENSSH)) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION_GT(2,0)) + #define TEST_EVP_PKEY_DUP_DH +#endif + +#if defined(TEST_EVP_PKEY_DUP_DSA) || defined(TEST_EVP_PKEY_DUP_DH) +/* Check dup carries the same cached DER and the same private/public state, + * and that it left the error queue clean. */ +static int test_EVP_PKEY_dup_der_check(EVP_PKEY* src, int id, int priv) +{ + EXPECT_DECLS; + EVP_PKEY* dup = NULL; + + ERR_clear_error(); + ExpectNotNull(dup = EVP_PKEY_dup(src)); + /* A successful dup must not leave anything on the error queue. */ + ExpectIntEQ(ERR_peek_error(), 0); + ExpectPtrNE(dup, src); + ExpectIntEQ(EVP_PKEY_id(dup), id); + ExpectIntEQ(dup->isPriv, priv); + ExpectIntEQ(dup->pkey_sz, src->pkey_sz); + ExpectIntEQ(XMEMCMP(dup->pkey.ptr, src->pkey.ptr, (size_t)src->pkey_sz), + 0); + + EVP_PKEY_free(dup); + + return EXPECT_RESULT(); +} +#endif /* TEST_EVP_PKEY_DUP_DSA || TEST_EVP_PKEY_DUP_DH */ + +int test_wolfSSL_EVP_PKEY_dup(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) + EVP_PKEY* key = NULL; + EVP_PKEY* dup = NULL; + const unsigned char* in; +#if !defined(NO_RSA) && defined(USE_CERT_BUFFERS_2048) && \ + defined(WOLFSSL_KEY_TO_DER) + EVP_PKEY* set1 = NULL; + RSA* rsa = NULL; +#endif + +#if !defined(NO_RSA) && defined(USE_CERT_BUFFERS_2048) + in = client_key_der_2048; + ExpectNotNull(key = wolfSSL_d2i_PrivateKey(EVP_PKEY_RSA, NULL, &in, + (long)sizeof_client_key_der_2048)); + ExpectIntEQ(test_EVP_PKEY_dup_check(&key, EVP_PKEY_RSA, 1), + TEST_SUCCESS); + EVP_PKEY_free(key); + key = NULL; + + in = client_keypub_der_2048; + ExpectNotNull(key = d2i_PUBKEY(NULL, &in, + (long)sizeof_client_keypub_der_2048)); + ExpectIntEQ(test_EVP_PKEY_dup_check(&key, EVP_PKEY_RSA, 0), + TEST_SUCCESS); + EVP_PKEY_free(key); + key = NULL; +#endif + +#if defined(HAVE_ECC) && defined(USE_CERT_BUFFERS_256) + in = ecc_clikey_der_256; + ExpectNotNull(key = wolfSSL_d2i_PrivateKey(EVP_PKEY_EC, NULL, &in, + (long)sizeof_ecc_clikey_der_256)); + ExpectIntEQ(test_EVP_PKEY_dup_check(&key, EVP_PKEY_EC, 1), + TEST_SUCCESS); + EVP_PKEY_free(key); + key = NULL; + + in = ecc_clikeypub_der_256; + ExpectNotNull(key = d2i_PUBKEY(NULL, &in, + (long)sizeof_ecc_clikeypub_der_256)); + ExpectIntEQ(test_EVP_PKEY_dup_check(&key, EVP_PKEY_EC, 0), + TEST_SUCCESS); + EVP_PKEY_free(key); + key = NULL; +#endif + +#ifdef TEST_EVP_PKEY_DUP_DSA + in = dsa_key_der_2048; + ExpectNotNull(key = d2i_PrivateKey(EVP_PKEY_DSA, NULL, &in, + (long)sizeof_dsa_key_der_2048)); + ExpectIntEQ(test_EVP_PKEY_dup_der_check(key, EVP_PKEY_DSA, 1), + TEST_SUCCESS); + EVP_PKEY_free(key); + key = NULL; + + in = dsa_pub_key_der_2048; + ExpectNotNull(key = d2i_PublicKey(EVP_PKEY_DSA, NULL, &in, + (long)sizeof_dsa_pub_key_der_2048)); + ExpectIntEQ(test_EVP_PKEY_dup_der_check(key, EVP_PKEY_DSA, 0), + TEST_SUCCESS); + EVP_PKEY_free(key); + key = NULL; +#endif /* TEST_EVP_PKEY_DUP_DSA */ + +#ifdef TEST_EVP_PKEY_DUP_DH + in = dh_key_der_2048; + ExpectNotNull(key = d2i_PrivateKey(EVP_PKEY_DH, NULL, &in, + (long)sizeof_dh_key_der_2048)); + ExpectIntEQ(test_EVP_PKEY_dup_der_check(key, EVP_PKEY_DH, 1), + TEST_SUCCESS); + EVP_PKEY_free(key); + key = NULL; +#endif /* TEST_EVP_PKEY_DUP_DH */ + + /* HMAC key */ + ExpectNotNull(key = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, + (const unsigned char*)"password", 8)); + ExpectNotNull(dup = EVP_PKEY_dup(key)); + ExpectPtrNE(dup, key); + ExpectIntEQ(EVP_PKEY_id(dup), EVP_PKEY_HMAC); + ExpectIntEQ(dup->pkey_sz, 8); + ExpectIntEQ(XMEMCMP(dup->pkey.ptr, "password", 8), 0); + EVP_PKEY_free(dup); + dup = NULL; + EVP_PKEY_free(key); + key = NULL; + +#if !defined(NO_RSA) && defined(USE_CERT_BUFFERS_2048) && \ + defined(WOLFSSL_KEY_TO_DER) + /* EVP_PKEY_set1_RSA() re-encodes the RSA key, so the flag has to follow + * the encoder it picks. */ + in = client_keypub_der_2048; + ExpectNotNull(key = d2i_PUBKEY(NULL, &in, + (long)sizeof_client_keypub_der_2048)); + ExpectNotNull(rsa = EVP_PKEY_get1_RSA(key)); + ExpectNotNull(set1 = EVP_PKEY_new()); + ExpectIntEQ(EVP_PKEY_set1_RSA(set1, rsa), WOLFSSL_SUCCESS); + ExpectIntEQ(set1->isPriv, 0); + ExpectNotNull(dup = EVP_PKEY_dup(set1)); + ExpectIntEQ(dup->isPriv, 0); + EVP_PKEY_free(dup); + dup = NULL; + EVP_PKEY_free(set1); + set1 = NULL; + RSA_free(rsa); + rsa = NULL; + EVP_PKEY_free(key); + key = NULL; + + in = client_key_der_2048; + ExpectNotNull(key = d2i_PrivateKey(EVP_PKEY_RSA, NULL, &in, + (long)sizeof_client_key_der_2048)); + ExpectNotNull(rsa = EVP_PKEY_get1_RSA(key)); + ExpectNotNull(set1 = EVP_PKEY_new()); + ExpectIntEQ(EVP_PKEY_set1_RSA(set1, rsa), WOLFSSL_SUCCESS); + ExpectIntEQ(set1->isPriv, 1); + ExpectNotNull(dup = EVP_PKEY_dup(set1)); + ExpectIntEQ(dup->isPriv, 1); + EVP_PKEY_free(dup); + dup = NULL; + EVP_PKEY_free(set1); + set1 = NULL; + RSA_free(rsa); + rsa = NULL; + EVP_PKEY_free(key); + key = NULL; +#endif /* !NO_RSA && USE_CERT_BUFFERS_2048 && WOLFSSL_KEY_TO_DER */ + + /* The constructors record whether the key holds private material. */ +#if !defined(NO_RSA) && defined(USE_CERT_BUFFERS_2048) + in = client_key_der_2048; + ExpectNotNull(key = d2i_PrivateKey(EVP_PKEY_RSA, NULL, &in, + (long)sizeof_client_key_der_2048)); + ExpectIntEQ(key->isPriv, 1); + EVP_PKEY_free(key); + key = NULL; + + in = client_keypub_der_2048; + ExpectNotNull(key = d2i_PublicKey(EVP_PKEY_RSA, NULL, &in, + (long)sizeof_client_keypub_der_2048)); + ExpectIntEQ(key->isPriv, 0); + EVP_PKEY_free(key); + key = NULL; +#endif + ExpectNotNull(key = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, + (const unsigned char*)"password", 8)); + ExpectIntEQ(key->isPriv, 1); + EVP_PKEY_free(key); + key = NULL; + + /* A new key holds nothing, so it is public by default. */ + ExpectNotNull(key = EVP_PKEY_new()); + ExpectIntEQ(key->isPriv, 0); + EVP_PKEY_free(key); + key = NULL; + + /* Bad cases */ + ExpectNull(EVP_PKEY_dup(NULL)); + ExpectNotNull(key = EVP_PKEY_new()); + ExpectNull(EVP_PKEY_dup(key)); + EVP_PKEY_free(key); +#endif /* OPENSSL_EXTRA */ + return EXPECT_RESULT(); +} + int test_wolfSSL_EVP_PKEY_set1_get1_DSA(void) { EXPECT_DECLS; diff --git a/tests/api/test_evp_pkey.h b/tests/api/test_evp_pkey.h index 8af6d24712e..2cbb3d3100e 100644 --- a/tests/api/test_evp_pkey.h +++ b/tests/api/test_evp_pkey.h @@ -37,6 +37,7 @@ int test_wolfSSL_EVP_PKEY_new_mac_key(void); int test_wolfSSL_EVP_PKEY_hkdf(void); int test_wolfSSL_EVP_PBE_scrypt(void); int test_EVP_PKEY_cmp(void); +int test_wolfSSL_EVP_PKEY_dup(void); int test_wolfSSL_EVP_PKEY_set1_get1_DSA(void); int test_wolfSSL_EVP_PKEY_set1_get1_EC_KEY (void); int test_wolfSSL_EVP_PKEY_get0_EC_KEY(void); @@ -94,6 +95,7 @@ int test_wolfSSL_CTX_use_PrivateKey_pkcs8_repopulate(void); TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_hkdf), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PBE_scrypt), \ TEST_DECL_GROUP("evp_pkey", test_EVP_PKEY_cmp), \ + TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_dup), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_set1_get1_DSA), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_set1_get1_EC_KEY), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_get0_EC_KEY), \ diff --git a/tests/api/test_ossl_asn1.c b/tests/api/test_ossl_asn1.c index c646141ed7d..597bdd216dd 100644 --- a/tests/api/test_ossl_asn1.c +++ b/tests/api/test_ossl_asn1.c @@ -1997,6 +1997,60 @@ int test_wolfSSL_ASN1_TIME_adj(void) return EXPECT_RESULT(); } +int test_wolfSSL_ASN1_TIME_set(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_ASN_TIME) && \ + !defined(USER_TIME) && !defined(TIME_OVERRIDES) + const int year = 365*24*60*60; + const int day = 24*60*60; + const int hour = 60*60; + const int mini = 60; + WOLFSSL_ASN1_TIME* s = NULL; + WOLFSSL_ASN1_TIME* asn_time = NULL; + char date_str[CTC_DATE_SIZE + 1]; + time_t t; + + /* 2000/2/15 20:30:00 */ + t = (time_t)30 * year + 45 * day + 20 * hour + 30 * mini + 7 * day; + + ExpectNotNull(s = wolfSSL_ASN1_TIME_new()); + ExpectPtrEq(wolfSSL_ASN1_TIME_set(s, t), s); + if (s != NULL) { + ExpectIntEQ(s->type, ASN_UTC_TIME); + ExpectIntEQ(s->length, ASN_UTC_TIME_SIZE - 1); + XMEMCPY(date_str, s->data, CTC_DATE_SIZE); + date_str[CTC_DATE_SIZE] = '\0'; + ExpectIntEQ(XMEMCMP(date_str, "000215203000Z", 13), 0); + } + wolfSSL_ASN1_TIME_free(s); + s = NULL; + + /* Allocated when NULL passed in. */ + ExpectNotNull(asn_time = wolfSSL_ASN1_TIME_set(NULL, t)); + if (asn_time != NULL) { + ExpectIntEQ(asn_time->type, ASN_UTC_TIME); + ExpectIntEQ(XMEMCMP(asn_time->data, "000215203000Z", 13), 0); + } + wolfSSL_ASN1_TIME_free(asn_time); + asn_time = NULL; + +#if !defined(TIME_T_NOT_64BIT) && !defined(NO_64BIT) + /* 2055/03/01 09:00:00 uses GeneralizedTime. */ + t = (time_t)85 * year + 59 * day + 9 * hour + 21 * day; + ExpectNotNull(asn_time = wolfSSL_ASN1_TIME_set(NULL, t)); + if (asn_time != NULL) { + ExpectIntEQ(asn_time->type, ASN_GENERALIZED_TIME); + ExpectIntEQ(asn_time->length, ASN_GENERALIZED_TIME_SIZE - 1); + ExpectIntEQ(XMEMCMP(asn_time->data, "20550301090000Z", 15), 0); + } + wolfSSL_ASN1_TIME_free(asn_time); + asn_time = NULL; +#endif +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_ASN1_TIME_to_tm(void) { EXPECT_DECLS; diff --git a/tests/api/test_ossl_asn1.h b/tests/api/test_ossl_asn1.h index 7ae66eb0672..2afd8e2b393 100644 --- a/tests/api/test_ossl_asn1.h +++ b/tests/api/test_ossl_asn1.h @@ -50,6 +50,7 @@ int test_wolfSSL_ASN1_TIME(void); int test_wolfSSL_ASN1_TIME_to_string(void); int test_wolfSSL_ASN1_TIME_diff_compare(void); int test_wolfSSL_ASN1_TIME_adj(void); +int test_wolfSSL_ASN1_TIME_set(void); int test_wolfSSL_ASN1_TIME_to_tm(void); int test_wolfSSL_ASN1_TIME_to_generalizedtime(void); int test_wolfSSL_ASN1_TIME_print(void); @@ -97,6 +98,7 @@ int test_ASN1_strings(void); TEST_DECL_GROUP("ossl_asn1_tm", test_wolfSSL_ASN1_TIME_to_string), \ TEST_DECL_GROUP("ossl_asn1_tm", test_wolfSSL_ASN1_TIME_diff_compare), \ TEST_DECL_GROUP("ossl_asn1_tm", test_wolfSSL_ASN1_TIME_adj), \ + TEST_DECL_GROUP("ossl_asn1_tm", test_wolfSSL_ASN1_TIME_set), \ TEST_DECL_GROUP("ossl_asn1_tm", test_wolfSSL_ASN1_TIME_to_tm), \ TEST_DECL_GROUP("ossl_asn1_tm", \ test_wolfSSL_ASN1_TIME_to_generalizedtime), \ diff --git a/tests/api/test_ossl_bn.c b/tests/api/test_ossl_bn.c index 46d3f67cd40..5fe7ef19725 100644 --- a/tests/api/test_ossl_bn.c +++ b/tests/api/test_ossl_bn.c @@ -532,9 +532,25 @@ int test_wolfSSL_BN_bits(void) ExpectIntEQ(BN_set_bit(a, 129), 1); ExpectIntEQ(BN_get_word(a), WOLFSSL_BN_MAX_VAL); -#ifndef NO_WOLFSSL_STUB - ExpectIntEQ(BN_mask_bits(a, 1), 0); -#endif + /* Invalid parameters. */ + ExpectIntEQ(BN_mask_bits(NULL, 1), 0); + ExpectIntEQ(BN_mask_bits(&emptyBN, 1), 0); + ExpectIntEQ(BN_mask_bits(a, -1), 0); + + /* a = 2^129 + 2 */ + ExpectIntEQ(BN_mask_bits(a, 128), 1); + ExpectIntEQ(BN_num_bits(a), 2); + ExpectIntEQ(BN_get_word(a), 2); + /* No change when a already fits. */ + ExpectIntEQ(BN_mask_bits(a, 200), 1); + ExpectIntEQ(BN_get_word(a), 2); + ExpectIntEQ(BN_mask_bits(a, 1), 1); + ExpectIntEQ(BN_is_zero(a), 1); + ExpectIntEQ(BN_set_word(a, 0xff), 1); + ExpectIntEQ(BN_mask_bits(a, 4), 1); + ExpectIntEQ(BN_get_word(a), 0xf); + ExpectIntEQ(BN_mask_bits(a, 0), 1); + ExpectIntEQ(BN_is_zero(a), 1); BN_free(a); #endif diff --git a/tests/api/test_ossl_dh.c b/tests/api/test_ossl_dh.c index 76b0da42f07..7f77208636d 100644 --- a/tests/api/test_ossl_dh.c +++ b/tests/api/test_ossl_dh.c @@ -89,6 +89,32 @@ int test_wolfSSL_DH(void) ExpectIntEQ(DH_compute_key_padded(buf2, dh->pub_key, dh2), sz1); ExpectIntEQ(XMEMCMP(buf, buf2, (size_t)sz1), 0); + /* Key agreement with only p and priv_key set (no g). */ + if (EXPECT_SUCCESS()) { + DH *dh3 = NULL; + + ExpectNotNull(dh3 = DH_new()); + if (dh3 != NULL) { + ExpectNotNull(dh3->p = BN_dup(dh->p)); + ExpectNotNull(dh3->priv_key = BN_dup(dh->priv_key)); + } + ExpectIntEQ(DH_compute_key_padded(buf2, dh2->pub_key, dh3), sz1); + ExpectIntEQ(XMEMCMP(buf, buf2, (size_t)sz1), 0); + /* Encoding and duplicating parameters still need g. */ + ExpectIntEQ(i2d_DHparams(dh3, NULL), 0); + ExpectNull(DHparams_dup(dh3)); + /* Adding g afterwards must give a usable key pair. */ + if (dh3 != NULL) { + ExpectNotNull(dh3->g = BN_dup(dh->g)); + } + ExpectIntEQ(DH_generate_key(dh3), 1); + if (dh3 != NULL) { + ExpectIntNE(BN_is_zero(dh3->pub_key), 1); + ExpectIntGT(DH_compute_key(buf2, dh3->pub_key, dh2), 0); + } + DH_free(dh3); + } + if (dh2 != NULL) DH_free(dh2); } diff --git a/tests/api/test_ossl_x509_ext.c b/tests/api/test_ossl_x509_ext.c index af2bedc46e2..16cbc2e5c74 100644 --- a/tests/api/test_ossl_x509_ext.c +++ b/tests/api/test_ossl_x509_ext.c @@ -1016,25 +1016,22 @@ int test_wolfSSL_X509V3_set_ctx(void) wolfSSL_X509V3_set_ctx(NULL, NULL, NULL, NULL, NULL, 0); wolfSSL_X509V3_set_ctx(&ctx, NULL, NULL, NULL, NULL, 0); - wolfSSL_X509_free(ctx.x509); - ctx.x509 = NULL; + ExpectNull(ctx.issuer); + ExpectNull(ctx.subject); wolfSSL_X509V3_set_ctx(&ctx, issuer, NULL, NULL, NULL, 0); - wolfSSL_X509_free(ctx.x509); - ctx.x509 = NULL; + ExpectPtrEq(ctx.issuer, issuer); + ExpectNull(ctx.subject); wolfSSL_X509V3_set_ctx(&ctx, NULL, subject, NULL, NULL, 0); - wolfSSL_X509_free(ctx.x509); - ctx.x509 = NULL; - wolfSSL_X509V3_set_ctx(&ctx, NULL, NULL, &req, NULL, 0); - wolfSSL_X509_free(ctx.x509); - ctx.x509 = NULL; - wolfSSL_X509V3_set_ctx(&ctx, NULL, NULL, NULL, &crl, 0); - wolfSSL_X509_free(ctx.x509); - ctx.x509 = NULL; - wolfSSL_X509V3_set_ctx(&ctx, NULL, NULL, NULL, NULL, 1); - /* X509 allocated in context results in 'failure' (but not return). */ - wolfSSL_X509V3_set_ctx(&ctx, NULL, NULL, NULL, NULL, 0); - wolfSSL_X509_free(ctx.x509); - ctx.x509 = NULL; + ExpectNull(ctx.issuer); + ExpectPtrEq(ctx.subject, subject); + wolfSSL_X509V3_set_ctx(&ctx, issuer, subject, &req, &crl, 1); + ExpectPtrEq(ctx.issuer, issuer); + ExpectPtrEq(ctx.subject, subject); + /* No zero init required. */ + XMEMSET(&ctx, 0xff, sizeof(ctx)); + wolfSSL_X509V3_set_ctx(&ctx, issuer, subject, NULL, NULL, 0); + ExpectPtrEq(ctx.issuer, issuer); + ExpectPtrEq(ctx.subject, subject); wolfSSL_X509_free(subject); wolfSSL_X509_free(issuer); @@ -1144,6 +1141,7 @@ int test_wolfSSL_X509V3_EXT_nconf(void) size_t i; X509_EXTENSION* ext = NULL; X509* x509 = NULL; + X509* x509Crit = NULL; unsigned int keyUsageFlags; unsigned int extKeyUsageFlags; WOLFSSL_CONF conf; @@ -1151,6 +1149,7 @@ int test_wolfSSL_X509V3_EXT_nconf(void) #ifndef NO_WOLFSSL_STUB WOLFSSL_LHASH lhash; #endif + char bcTooLong[32]; ExpectNotNull(x509 = X509_new()); ExpectNull(X509V3_EXT_nconf(NULL, NULL, ext_names[0], NULL)); @@ -1195,6 +1194,59 @@ int test_wolfSSL_X509V3_EXT_nconf(void) ext = NULL; } + /* basicConstraints from string */ + ExpectNotNull(ext = X509V3_EXT_nconf_nid(NULL, NULL, NID_basic_constraints, + "CA:FALSE")); + if (ext != NULL) { + ExpectNotNull(ext->obj); + ExpectIntEQ(ext->obj->type, NID_basic_constraints); + ExpectIntEQ(ext->obj->ca, 0); + ExpectNull(ext->obj->pathlen); + ExpectIntEQ(ext->crit, 0); + } + X509_EXTENSION_free(ext); + ext = NULL; + ExpectNotNull(ext = X509V3_EXT_nconf_nid(NULL, NULL, + NID_basic_constraints, "critical, CA:TRUE, pathlen:2")); + if (ext != NULL) { + ExpectNotNull(ext->obj); + ExpectIntEQ(ext->obj->ca, 1); + ExpectNotNull(ext->obj->pathlen); + ExpectIntEQ(ext->obj->pathlen->length, 2); + ExpectIntEQ(ext->crit, 1); + } + ExpectIntEQ(X509_add_ext(x509, ext, -1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_get_pathLength(x509), 2); + X509_EXTENSION_free(ext); + ext = NULL; + ExpectNull(X509V3_EXT_nconf_nid(NULL, NULL, NID_basic_constraints, + "CA:MAYBE")); + ExpectNull(X509V3_EXT_nconf_nid(NULL, NULL, NID_basic_constraints, + "pathlen:1")); + ExpectNull(X509V3_EXT_nconf_nid(NULL, NULL, NID_basic_constraints, + "CA:TRUE,pathlen:x")); + XSNPRINTF(bcTooLong, sizeof(bcTooLong), "CA:TRUE,pathlen:%d", + WOLFSSL_MAX_PATH_LEN + 1); + ExpectNull(X509V3_EXT_nconf_nid(NULL, NULL, NID_basic_constraints, + bcTooLong)); + /* critical is only accepted as a prefix, and only once. */ + ExpectNull(X509V3_EXT_nconf_nid(NULL, NULL, NID_basic_constraints, + "CA:TRUE,critical")); + ExpectNull(X509V3_EXT_nconf_nid(NULL, NULL, NID_basic_constraints, + "critical,critical,CA:TRUE")); + + /* The critical prefix is handled for every extension type. */ + ExpectNotNull(ext = X509V3_EXT_nconf_nid(NULL, NULL, NID_key_usage, + "critical,digitalSignature")); + ExpectIntEQ(ext->crit, 1); + ExpectNotNull(x509Crit = X509_new()); + ExpectIntEQ(X509_add_ext(x509Crit, ext, -1), WOLFSSL_SUCCESS); + ExpectIntEQ(X509_get_key_usage(x509Crit), KU_DIGITAL_SIGNATURE); + X509_free(x509Crit); + x509Crit = NULL; + X509_EXTENSION_free(ext); + ext = NULL; + /* Test adding extension to X509 */ for (i = 0; i < ext_nids_count; i++) { ExpectNotNull(ext = X509V3_EXT_nconf(NULL, NULL, ext_names[i], @@ -1451,6 +1503,11 @@ int test_wolfSSL_X509V3_EXT_aia(void) wolfSSL_AUTHORITY_INFO_ACCESS_pop_free(aia, wolfSSL_ACCESS_DESCRIPTION_free); + aia = NULL; + /* Plain free releases the entries too. */ + ExpectNotNull(aia = (WOLFSSL_AUTHORITY_INFO_ACCESS *) + wolfSSL_X509V3_EXT_d2i(ext)); + wolfSSL_AUTHORITY_INFO_ACCESS_free(aia); wolfSSL_ASN1_OBJECT_free(entry); wolfSSL_sk_free(node); wolfSSL_ASN1_OBJECT_free(obj); diff --git a/tests/api/test_ossl_x509_vp.c b/tests/api/test_ossl_x509_vp.c index f3b463b36e2..0abcd96e740 100644 --- a/tests/api/test_ossl_x509_vp.c +++ b/tests/api/test_ossl_x509_vp.c @@ -74,9 +74,11 @@ int test_wolfSSL_X509_VERIFY_PARAM(void) (int)XSTRLEN(testhostName1)), 1); X509_VERIFY_PARAM_set_hostflags(NULL, 0x00); + ExpectIntEQ(X509_VERIFY_PARAM_get_hostflags(NULL), 0); X509_VERIFY_PARAM_set_hostflags(paramFrom, 0x01); ExpectIntEQ(0x01, paramFrom->hostFlags); + ExpectIntEQ(X509_VERIFY_PARAM_get_hostflags(paramFrom), 0x01); ExpectIntEQ(X509_VERIFY_PARAM_set1_ip_asc(NULL, testIPv4), 0); @@ -96,10 +98,12 @@ int test_wolfSSL_X509_VERIFY_PARAM(void) ExpectIntEQ(X509_VERIFY_PARAM_set1(NULL, NULL), 0); /* inherit flags test : VPARAM_DEFAULT */ + ExpectIntEQ(X509_VERIFY_PARAM_get_hostflags(paramTo), 0); ExpectIntEQ(X509_VERIFY_PARAM_set1(paramTo, paramFrom), 1); ExpectIntEQ(0, XSTRNCMP(paramTo->hostName, testhostName1, (int)XSTRLEN(testhostName1))); ExpectIntEQ(0x01, paramTo->hostFlags); + ExpectIntEQ(X509_VERIFY_PARAM_get_hostflags(paramTo), 0x01); ExpectIntEQ(0, XSTRNCMP(paramTo->ipasc, testIPv6, WOLFSSL_MAX_IPSTR)); /* inherit flags test : VPARAM OVERWRITE */ diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index ca0ccdd7432..3eb806d1f47 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -1267,6 +1267,15 @@ int wolfSSL_EVP_CipherUpdate(WOLFSSL_EVP_CIPHER_CTX *ctx, /* put fraction into buff */ fillBuff(ctx, in, inl); /* no increase of outl */ + + /* Decrypting with padding: a block followed by buffered bytes can not + * be the last one. Output it now so that the next update writes at + * most inl + block_size bytes, as OpenSSL does. */ + if ((ctx->enc == 0) && (ctx->lastUsed == 1)) { + XMEMCPY(out, ctx->lastBlock, (size_t)ctx->block_size); + *outl += ctx->block_size; + ctx->lastUsed = 0; + } } (void)out; /* silence warning in case not read */ @@ -4015,6 +4024,7 @@ int wolfSSL_EVP_PKEY_keygen(WOLFSSL_EVP_PKEY_CTX *ctx, pkey->pkey.ptr = (char*)edDer; pkey->pkey_sz = edDerSz; pkey->pkcs8HeaderSz = (word16)hdrIdx; + pkey->isPriv = 1; ret = WOLFSSL_SUCCESS; } else { @@ -4794,6 +4804,7 @@ WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_new_mac_key(int type, WOLFSSL_ENGINE* e, } pkey->pkey_sz = keylen; pkey->type = pkey->save_type = type; + pkey->isPriv = 1; } } @@ -4845,6 +4856,7 @@ WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_new_CMAC_key(WOLFSSL_ENGINE* e, pkey->pkey_sz = (int)len; pkey->type = pkey->save_type = WC_EVP_PKEY_CMAC; pkey->cmacCtx = ctx; + pkey->isPriv = 1; } } else { @@ -9335,6 +9347,7 @@ static void clearEVPPkeyKeys(WOLFSSL_EVP_PKEY *pkey) if(pkey == NULL) return; WOLFSSL_ENTER("clearEVPPkeyKeys"); + pkey->isPriv = 0; #ifndef NO_RSA if (pkey->rsa != NULL && pkey->ownRsa == 1) { wolfSSL_RSA_free(pkey->rsa); @@ -9560,6 +9573,7 @@ static int PopulateRSAEvpPkeyDer(WOLFSSL_EVP_PKEY *pkey) } else { pkey->pkey_sz = derSz; + pkey->isPriv = (rsa->type == RSA_PRIVATE); return WOLFSSL_SUCCESS; } } @@ -9699,6 +9713,7 @@ int wolfSSL_EVP_PKEY_set1_DSA(WOLFSSL_EVP_PKEY *pkey, WOLFSSL_DSA *key) return WOLFSSL_FAILURE; } pkey->pkey_sz = derSz; + pkey->isPriv = (dsa->type == DSA_PRIVATE); XMEMCPY(pkey->pkey.ptr, derBuf, (size_t)derSz); XFREE(derBuf, pkey->heap, DYNAMIC_TYPE_TMP_BUFFER); @@ -9898,6 +9913,7 @@ int wolfSSL_EVP_PKEY_set1_DH(WOLFSSL_EVP_PKEY *pkey, WOLFSSL_DH *key) /* Store DH key into pkey (DER format) */ pkey->pkey.ptr = (char*)derBuf; pkey->pkey_sz = (int)derSz; + pkey->isPriv = (havePrivate != 0); return WOLFSSL_SUCCESS; } @@ -10034,6 +10050,7 @@ static int ECC_populate_EVP_PKEY(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_EC_KEY *key) pkey->pkey_sz = (int)derSz; pkey->pkey.ptr = (char*)derBuf; pkey->pkcs8HeaderSz = key->pkcs8HeaderSz; + pkey->isPriv = 1; return WOLFSSL_SUCCESS; } else { @@ -10083,6 +10100,7 @@ static int ECC_populate_EVP_PKEY(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_EC_KEY *key) * it, or the export paths start inside the new * encoding. */ pkey->pkcs8HeaderSz = 0; + pkey->isPriv = 1; return WOLFSSL_SUCCESS; } else { @@ -10150,6 +10168,7 @@ static int ECC_populate_EVP_PKEY(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_EC_KEY *key) } if (derBuf != NULL) { pkey->pkey_sz = (int)derSz; + pkey->isPriv = 0; return WOLFSSL_SUCCESS; } else { @@ -10200,6 +10219,7 @@ void* wolfSSL_EVP_X_STATE(const WOLFSSL_EVP_CIPHER_CTX* ctx) int wolfSSL_EVP_PKEY_assign_EC_KEY(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_EC_KEY* key) { int ret; + int isPriv; if (pkey == NULL || key == NULL) return WOLFSSL_FAILURE; @@ -10207,7 +10227,11 @@ int wolfSSL_EVP_PKEY_assign_EC_KEY(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_EC_KEY* key) /* try and populate public pkey_sz and pkey.ptr */ ret = ECC_populate_EVP_PKEY(pkey, key); if (ret == WOLFSSL_SUCCESS) { /* take ownership of key if can be used */ + /* The encoding just cached describes the new key, so carry its + * private/public state over the clear below. */ + isPriv = pkey->isPriv; clearEVPPkeyKeys(pkey); /* clear out any previous keys */ + pkey->isPriv = isPriv; pkey->type = WC_EVP_PKEY_EC; pkey->ecc = key; @@ -11006,6 +11030,7 @@ int wolfSSL_EVP_PKEY_assign_RSA(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_RSA* key) if (ret >= 0) { pkey->pkey_sz = ret; pkey->pkey.ptr = (char*)derBuf; + pkey->isPriv = 1; } else { /* failure - okay to ignore */ XFREE(derBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/wolfcrypt/src/evp_pk.c b/wolfcrypt/src/evp_pk.c index c0347f95b14..e25c0da46c9 100644 --- a/wolfcrypt/src/evp_pk.c +++ b/wolfcrypt/src/evp_pk.c @@ -55,8 +55,6 @@ static int d2i_make_pkey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, int prevSz = 0; int ret = 1; - (void)priv; - /* Get or create the EVP PKEY object. */ if (*out != NULL) { pkey = *out; @@ -120,6 +118,7 @@ static int d2i_make_pkey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, if (ret == 1) { /* Set key type passed in and return object. */ pkey->type = type; + pkey->isPriv = (priv != 0); *out = pkey; } if ((ret == 0) && (*out == NULL)) { @@ -575,6 +574,7 @@ WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_new_raw_public_key(int type, } XMEMCPY(pkey->pkey.ptr, pub, len); pkey->pkey_sz = (int)len; + pkey->isPriv = 0; return pkey; } @@ -591,6 +591,16 @@ WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_new_raw_private_key(int type, (void)e; WOLFSSL_ENTER("wolfSSL_EVP_PKEY_new_raw_private_key"); + #ifdef OPENSSL_EXTRA + /* HMAC keys are raw octets, same as EVP_PKEY_new_mac_key. */ + if (type == WC_EVP_PKEY_HMAC) { + if (len > (size_t)INT_MAX) { + return NULL; + } + return wolfSSL_EVP_PKEY_new_mac_key(type, e, priv, (int)len); + } + #endif /* OPENSSL_EXTRA */ + if (priv == NULL || len == 0) { return NULL; } @@ -735,6 +745,7 @@ WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_new_raw_private_key(int type, } XMEMCPY(pkey->pkey.ptr, priv, len); pkey->pkey_sz = (int)len; + pkey->isPriv = 1; return pkey; } @@ -1539,6 +1550,7 @@ static WOLFSSL_EVP_PKEY* d2i_evp_pkey(int type, WOLFSSL_EVP_PKEY** out, local->type = type; local->pkey_sz = (int)inSz; local->pkcs8HeaderSz = pkcs8HeaderSz; + local->isPriv = (priv != 0); local->pkey.ptr = (char*)XMALLOC((size_t)inSz, NULL, DYNAMIC_TYPE_PUBLIC_KEY); if (local->pkey.ptr == NULL) { @@ -1703,6 +1715,45 @@ WOLFSSL_EVP_PKEY* wolfSSL_d2i_PrivateKey(int type, WOLFSSL_EVP_PKEY** out, return d2i_evp_pkey(type, out, in, inSz, 1); } + +/* Deep copy of a key by re-decoding its cached DER. + * + * @param [in] pkey Key to copy. + * @return New WOLFSSL_EVP_PKEY on success. + * @return NULL when pkey is NULL, holds no DER or decoding fails. + */ +WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_dup(const WOLFSSL_EVP_PKEY* pkey) +{ + WOLFSSL_EVP_PKEY* dup = NULL; + const unsigned char* der; + + WOLFSSL_ENTER("wolfSSL_EVP_PKEY_dup"); + + if (pkey == NULL || pkey->pkey.ptr == NULL || pkey->pkey_sz <= 0) { + WOLFSSL_MSG("No key data to duplicate"); + return NULL; + } + + der = (const unsigned char*)pkey->pkey.ptr; + if (pkey->type == WC_EVP_PKEY_HMAC) { + dup = wolfSSL_EVP_PKEY_new_mac_key(WC_EVP_PKEY_HMAC, NULL, der, + pkey->pkey_sz); + } + else { + /* Every path that caches DER records whether it is private, so the + * encoding is decoded the same way it was made. */ + dup = d2i_evp_pkey(pkey->type, NULL, &der, pkey->pkey_sz, + pkey->isPriv); + if (dup != NULL) { + #ifdef HAVE_ECC + dup->pkey_curve = pkey->pkey_curve; + #endif + dup->save_type = pkey->save_type; + } + } + + return dup; +} #endif /* OPENSSL_EXTRA */ #ifdef OPENSSL_ALL @@ -2079,6 +2130,7 @@ WOLFSSL_PKCS8_PRIV_KEY_INFO* wolfSSL_d2i_PKCS8_PKEY( /* Copy in DER data and size. */ XMEMCPY(pkcs8->pkey.ptr, rawDer.buffer, rawDer.length); pkcs8->pkey_sz = (int)rawDer.length; + pkcs8->isPriv = 1; } /* Dispose of PKCS#8 DER data - raw DER reference data in pkcs8Der. */ @@ -2176,6 +2228,7 @@ WOLFSSL_EVP_PKEY* wolfSSL_d2i_PrivateKey_id(int type, WOLFSSL_EVP_PKEY** out, local->type = type; local->pkey_sz = 0; local->pkcs8HeaderSz = 0; + local->isPriv = 1; switch (type) { #ifndef NO_RSA diff --git a/wolfcrypt/src/logging.c b/wolfcrypt/src/logging.c index 736adbebdb6..b6fb4c60fa4 100644 --- a/wolfcrypt/src/logging.c +++ b/wolfcrypt/src/logging.c @@ -728,6 +728,7 @@ struct wc_error_entry { char file[WOLFSSL_MAX_ERROR_SZ]; int line; int err; + int mark; /* set by wc_SetErrorMark */ }; struct wc_error_queue { @@ -956,6 +957,34 @@ int wc_ERR_remove_state(void) return 0; } +/* Mark the newest entry. Returns 0 if the queue is empty. */ +int wc_SetErrorMark(void) +{ + struct wc_error_entry *entry = get_entry(-1); + + if (entry == NULL) { + return 0; + } + entry->mark = 1; + return 1; +} + +/* Remove entries newer than the last mark and clear that mark. + * Returns 0 if no mark was found and the queue is now empty. */ +int wc_PopErrorMark(void) +{ + struct wc_error_entry *entry; + + while ((entry = get_entry(-1)) != NULL) { + if (entry->mark) { + entry->mark = 0; + return 1; + } + wc_RemoveErrorNode(-1); + } + return 0; +} + /** * Get the first entry's values in the ERR queue that is not filtered * by the provided `ignore_err` callback. All ignored entries are removed, @@ -1078,6 +1107,7 @@ struct wc_error_queue { char file[WOLFSSL_MAX_ERROR_SZ]; int value; int line; + int mark; /* set by wc_SetErrorMark */ }; /* The global list of errors encountered */ @@ -1477,6 +1507,45 @@ int wc_ERR_remove_state(void) return 0; } +/* Mark the newest node. Returns 0 if the queue is empty. */ +int wc_SetErrorMark(void) +{ + int ret = 0; + + if (ERRQ_LOCK() != 0) { + WOLFSSL_MSG("Lock debug mutex failed"); + return 0; + } + if (wc_last_node != NULL) { + wc_last_node->mark = 1; + ret = 1; + } + ERRQ_UNLOCK(); + return ret; +} + +/* Remove nodes newer than the last mark and clear that mark. + * Returns 0 if no mark was found and the queue is now empty. */ +int wc_PopErrorMark(void) +{ + int ret = 0; + + if (ERRQ_LOCK() != 0) { + WOLFSSL_MSG("Lock debug mutex failed"); + return 0; + } + while (wc_last_node != NULL) { + if (wc_last_node->mark) { + wc_last_node->mark = 0; + ret = 1; + break; + } + removeErrorNode(-1); + } + ERRQ_UNLOCK(); + return ret; +} + unsigned long wc_PeekErrorNodeLineData(const char **file, int *line, const char **data, int *flags, int (*ignore_err)(int err)) @@ -1680,6 +1749,16 @@ int wc_ERR_remove_state(void) return 0; } +int wc_SetErrorMark(void) +{ + return 0; +} + +int wc_PopErrorMark(void) +{ + return 0; +} + /* Returns 0 both when the error queue is empty and when * WOLFSSL_HAVE_ERROR_QUEUE is not compiled in. */ unsigned long wc_PeekErrorNodeLineData(const char **file, int *line, diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 4310aefe208..3328f32d105 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -35771,13 +35771,14 @@ static wc_test_ret_t openssl_aes_cbc_test(void) if (wolfSSL_EVP_CipherUpdate(de, (byte*)&plain[total], &outlen, (byte*)&cipher[6], 12) == 0) return WC_TEST_RET_ENC_NC; - if (outlen != 0) + if (outlen != 16) + return WC_TEST_RET_ENC_NC; total += outlen; if (wolfSSL_EVP_CipherUpdate(de, (byte*)&plain[total], &outlen, (byte*)&cipher[6+12], 14) == 0) return WC_TEST_RET_ENC_NC; - if (outlen != 16) + if (outlen != 0) return WC_TEST_RET_ENC_NC; total += outlen; @@ -37627,13 +37628,14 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t openssl_test(void) if (wolfSSL_EVP_CipherUpdate(de, (byte*)&plain[total], &outlen, (byte*)&cipher[6], 12) == 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); - if(outlen != 0) + if(outlen != 16) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); total += outlen; if (wolfSSL_EVP_CipherUpdate(de, (byte*)&plain[total], &outlen, (byte*)&cipher[6+12], 14) == 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); - if(outlen != 16) + if(outlen != 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); total += outlen; @@ -37692,12 +37694,13 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t openssl_test(void) if (wolfSSL_EVP_CipherUpdate(de, (byte*)&plain[total], &outlen, (byte*)&cipher[6], 12) == 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); - if(outlen != 0) + if(outlen != 16) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); total += outlen; if (wolfSSL_EVP_CipherUpdate(de, (byte*)&plain[total], &outlen, (byte*)&cipher[6+12], 14) == 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); - if(outlen != 16) + if(outlen != 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); total += outlen; diff --git a/wolfssl/internal.h b/wolfssl/internal.h index f8fe59be7a0..f7a751d86b7 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2574,6 +2574,9 @@ WOLFSSL_LOCAL int SetCipherList(const WOLFSSL_CTX* ctx, Suites* suites, const char* list); WOLFSSL_LOCAL int SetCipherListFromBytes(WOLFSSL_CTX* ctx, Suites* suites, const byte* list, const int listSz); +#if defined(HAVE_ANON) && (defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL)) +WOLFSSL_LOCAL int SuitesHaveAnon(const Suites* suites); +#endif WOLFSSL_LOCAL int SetSuitesHashSigAlgo(Suites* suites, const char* list); #ifndef PSK_TYPES_DEFINED diff --git a/wolfssl/openssl/bn.h b/wolfssl/openssl/bn.h index 7371f8a03ac..badb69d6341 100644 --- a/wolfssl/openssl/bn.h +++ b/wolfssl/openssl/bn.h @@ -140,7 +140,9 @@ WOLFSSL_API int wolfSSL_BN_bn2binpad(const WOLFSSL_BIGNUM* bn, unsigned char* r, WOLFSSL_API WOLFSSL_BIGNUM* wolfSSL_BN_bin2bn(const unsigned char* str, int len, WOLFSSL_BIGNUM* ret); +#ifndef WOLFSSL_SP_MATH WOLFSSL_API int wolfSSL_mask_bits(WOLFSSL_BIGNUM* bn, int n); +#endif WOLFSSL_API int wolfSSL_BN_pseudo_rand(WOLFSSL_BIGNUM* bn, int bits, int top, int bottom); @@ -263,7 +265,9 @@ typedef WOLFSSL_BN_GENCB BN_GENCB; #define BN_gcd wolfSSL_BN_gcd #define BN_value_one wolfSSL_BN_value_one +#ifndef WOLFSSL_SP_MATH #define BN_mask_bits wolfSSL_mask_bits +#endif #define BN_pseudo_rand wolfSSL_BN_pseudo_rand #define BN_rand wolfSSL_BN_rand diff --git a/wolfssl/openssl/evp.h b/wolfssl/openssl/evp.h index 74e8a8ba126..bffa7f400f5 100644 --- a/wolfssl/openssl/evp.h +++ b/wolfssl/openssl/evp.h @@ -493,6 +493,10 @@ enum { #define WOLFSSL_EVP_PKEY_PRINT_INDENT_MAX 128 +/* wolfSSL decodes RSA-PSS keys as WC_EVP_PKEY_RSA. This id is only for + * applications that switch on it alongside WC_EVP_PKEY_RSA. */ +#define WC_EVP_PKEY_RSA_PSS WC_NID_rsassaPss + #define WC_EVP_PKEY_OP_SIGN (1 << 3) #define WC_EVP_PKEY_OP_VERIFY (1 << 5) #define WC_EVP_PKEY_OP_ENCRYPT (1 << 6) @@ -518,6 +522,7 @@ enum { #define ARC4_TYPE WC_ARC4_TYPE #define NULL_CIPHER_TYPE WC_NULL_CIPHER_TYPE #define EVP_PKEY_RSA WC_EVP_PKEY_RSA +#define EVP_PKEY_RSA_PSS WC_EVP_PKEY_RSA_PSS #define EVP_PKEY_DSA WC_EVP_PKEY_DSA #define EVP_PKEY_EC WC_EVP_PKEY_EC #define AES_128_GCM_TYPE WC_AES_128_GCM_TYPE @@ -1022,6 +1027,7 @@ WOLFSSL_API WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_new_raw_private_key(int type, WOLFSSL_API void wolfSSL_EVP_PKEY_free(WOLFSSL_EVP_PKEY* key); WOLFSSL_API int wolfSSL_EVP_PKEY_size(WOLFSSL_EVP_PKEY *pkey); WOLFSSL_API int wolfSSL_EVP_PKEY_copy_parameters(WOLFSSL_EVP_PKEY *to, const WOLFSSL_EVP_PKEY *from); +WOLFSSL_API WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_dup(const WOLFSSL_EVP_PKEY* pkey); WOLFSSL_API int wolfSSL_EVP_PKEY_missing_parameters(WOLFSSL_EVP_PKEY *pkey); WOLFSSL_API int wolfSSL_EVP_PKEY_cmp(const WOLFSSL_EVP_PKEY *a, const WOLFSSL_EVP_PKEY *b); WOLFSSL_API int wolfSSL_EVP_PKEY_type(int type); @@ -1440,6 +1446,7 @@ WOLFSSL_API int wolfSSL_EVP_SignInit_ex(WOLFSSL_EVP_MD_CTX* ctx, #define EVP_PKEY_new_raw_private_key wolfSSL_EVP_PKEY_new_raw_private_key #define EVP_PKEY_free wolfSSL_EVP_PKEY_free #define EVP_PKEY_up_ref wolfSSL_EVP_PKEY_up_ref +#define EVP_PKEY_dup wolfSSL_EVP_PKEY_dup #define EVP_PKEY_size wolfSSL_EVP_PKEY_size #define EVP_PKEY_copy_parameters wolfSSL_EVP_PKEY_copy_parameters #define EVP_PKEY_missing_parameters wolfSSL_EVP_PKEY_missing_parameters diff --git a/wolfssl/openssl/ssl.h b/wolfssl/openssl/ssl.h index 762eb5833fa..a4172e6c200 100644 --- a/wolfssl/openssl/ssl.h +++ b/wolfssl/openssl/ssl.h @@ -821,6 +821,7 @@ wolfSSL_X509_STORE_set_verify_cb((WOLFSSL_X509_STORE *)(s), (WOLFSSL_X509_STORE_ #define X509_VERIFY_PARAM_get_flags wolfSSL_X509_VERIFY_PARAM_get_flags #define X509_VERIFY_PARAM_clear_flags wolfSSL_X509_VERIFY_PARAM_clear_flags #define X509_VERIFY_PARAM_set_hostflags wolfSSL_X509_VERIFY_PARAM_set_hostflags +#define X509_VERIFY_PARAM_get_hostflags wolfSSL_X509_VERIFY_PARAM_get_hostflags #define SSL_set1_host wolfSSL_set1_host #define X509_VERIFY_PARAM_set1_host wolfSSL_X509_VERIFY_PARAM_set1_host #define X509_VERIFY_PARAM_set1_ip_asc wolfSSL_X509_VERIFY_PARAM_set1_ip_asc @@ -1239,6 +1240,8 @@ typedef wolfSSL_custom_ext_parse_cb custom_ext_parse_cb; #define ERR_print_errors_cb wolfSSL_ERR_print_errors_cb #define ERR_print_errors wolfSSL_ERR_print_errors #define ERR_clear_error wolfSSL_ERR_clear_error +#define ERR_set_mark wolfSSL_ERR_set_mark +#define ERR_pop_to_mark wolfSSL_ERR_pop_to_mark #define ERR_free_strings wolfSSL_ERR_free_strings #define ERR_remove_state wolfSSL_ERR_remove_state #define ERR_remove_thread_state wolfSSL_ERR_remove_thread_state diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 69c7d802491..487046d7cfd 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -524,7 +524,8 @@ struct WOLFSSL_ACCESS_DESCRIPTION { }; struct WOLFSSL_X509V3_CTX { - WOLFSSL_X509* x509; + WOLFSSL_X509* issuer; + WOLFSSL_X509* subject; }; struct WOLFSSL_ASN1_OBJECT { @@ -645,6 +646,7 @@ struct WOLFSSL_EVP_PKEY { word16 pkcs8HeaderSz; /* option bits */ + WC_BITFIELD isPriv:1; /* key holds private material, 0 means public only */ WC_BITFIELD ownDh:1; /* if struct owns DH and should free it */ WC_BITFIELD ownEcc:1; /* if struct owns ECC and should free it */ WC_BITFIELD ownDsa:1; /* if struct owns DSA and should free it */ @@ -2568,6 +2570,8 @@ WOLFSSL_API int wolfSSL_X509_VERIFY_PARAM_clear_flags(WOLFSSL_X509_VERIFY_PARAM unsigned long flags); WOLFSSL_API void wolfSSL_X509_VERIFY_PARAM_set_hostflags( WOLFSSL_X509_VERIFY_PARAM* param, unsigned int flags); +WOLFSSL_API unsigned int wolfSSL_X509_VERIFY_PARAM_get_hostflags( + const WOLFSSL_X509_VERIFY_PARAM* param); WOLFSSL_API int wolfSSL_set1_host(WOLFSSL* ssl, const char * name); WOLFSSL_API int wolfSSL_X509_VERIFY_PARAM_set1_host(WOLFSSL_X509_VERIFY_PARAM* pParam, const char* name, @@ -3436,6 +3440,8 @@ WOLFSSL_API unsigned long wolfSSL_ERR_get_error_line_data(const char** file, int WOLFSSL_API unsigned long wolfSSL_ERR_get_error(void); WOLFSSL_API void wolfSSL_ERR_clear_error(void); +WOLFSSL_API int wolfSSL_ERR_set_mark(void); +WOLFSSL_API int wolfSSL_ERR_pop_to_mark(void); WOLFSSL_API int wolfSSL_RAND_status(void); diff --git a/wolfssl/wolfcrypt/logging.h b/wolfssl/wolfcrypt/logging.h index 631c1cb9acc..13542dfb1be 100644 --- a/wolfssl/wolfcrypt/logging.h +++ b/wolfssl/wolfcrypt/logging.h @@ -236,6 +236,8 @@ WOLFSSL_API void wolfSSL_SetLoggingPrefix(const char* prefix); int *line); WOLFSSL_API int wc_SetLoggingHeap(void* h); WOLFSSL_API int wc_ERR_remove_state(void); + WOLFSSL_LOCAL int wc_SetErrorMark(void); + WOLFSSL_LOCAL int wc_PopErrorMark(void); WOLFSSL_LOCAL unsigned long wc_PeekErrorNodeLineData( const char **file, int *line, const char **data, int *flags, int (*ignore_err)(int err));