jumpserver/jumpserver · error · UKeyCertNormalizationError

Invalid certificate format

Error message

Invalid certificate format

What it means

UKeyCertNormalizationError raised when writing the submitted cert_pem to a temp file and importing it via Sm2Certificate().import_pem fails with any exception — the PEM is not a parseable SM2/CSR certificate (wrong format, corrupted, or an RSA/standard X.509 PEM handed to the GM parser).

Source

Thrown at apps/authentication/backends/ukey/backends.py:103

        self._verify_cert_cn(sm2_cert.get_subject().get('commonName'), username)
        self._verify_sm2_signature(sm2_cert.get_subject_public_key(), signature, challenge)
        return user

    @staticmethod
    def _load_sm2_cert(cert_pem):
        """将 PEM 字符串写入临时文件,加载为 Sm2Certificate 对象后立即删除临时文件。"""
        from common.utils.gmssl_python import Sm2Certificate

        fd, cert_file = tempfile.mkstemp(suffix='.crt')
        try:
            os.close(fd)
            with open(cert_file, 'w', encoding='utf-8') as f:
                f.write(cert_pem)
            sm2_cert = Sm2Certificate()
            sm2_cert.import_pem(cert_file)
        except Exception as e:
            logger.error('UKeyBackend: failed to load SM2 cert: %s', e)
            raise UKeyCertNormalizationError()
        finally:
            if os.path.exists(cert_file):
                os.unlink(cert_file)
        return sm2_cert

    @staticmethod
    def _verify_sm2_cert_validity(sm2_cert):
        """校验 SM2 证书有效期(not_before / not_after)。"""
        try:
            validity = sm2_cert.get_validity()
        except Exception as e:
            logger.error('UKeyBackend: failed to get SM2 cert validity: %s', e)
            raise UKeyCertExpiredError()
        UKeyBackend._check_validity_period(validity.not_before, validity.not_after, 'SM2')

    @staticmethod
    def _verify_sm2_cert_chain(sm2_cert):
        """调用 Sm2Certificate.verify_by_ca_certificate 验证 SM2 证书链。"""

View on GitHub (pinned to 6ec464fabd)

Solutions

  1. Inspect the submitted cert_pem (log length/head/tail) and confirm it begins with BEGIN CERTIFICATE and is intact.
  2. Ensure the client sends the SM2 certificate PEM verbatim with real newlines.
  3. Route non-SM2 certificates to _authenticate_other instead of the SM2 branch.

Example fix

# before
cert_pem = base64.b64encode(raw_der).decode()  # DER b64, not PEM

# after
cert_pem = raw_der.decode() if b'BEGIN CERTIFICATE' in raw_der else pem_encode(raw_der)
Defensive patterns

Strategy: validation

Validate before calling

def is_valid_pem(cert_pem: str) -> bool:
    return isinstance(cert_pem, str) and '-----BEGIN CERTIFICATE-----' in cert_pem \
        and '-----END CERTIFICATE-----' in cert_pem

if not is_valid_pem(cert_pem):
    return error_response('Invalid certificate payload', code='BAD_CERT')

Type guard

def is_sm2_pem(cert_pem: str) -> bool:
    return (isinstance(cert_pem, str)
            and cert_pem.lstrip().startswith('-----BEGIN CERTIFICATE-----')
            and '\n' in cert_pem)

Try / catch

try:
    sm2_cert = UKeyBackend._load_sm2_cert(cert_pem)
except UKeyCertNormalizationError:
    return error_response('Certificate could not be parsed', status=400)

Prevention

When it happens

Trigger: _authenticate_sm2 receives cert_pem that gmssl cannot import: truncated payload, base64/DER instead of PEM, or a non-SM2 certificate submitted on the SM2 path.

Common situations: Client sends the wrong certificate slot, frontend mangles newlines in the PEM, double-encoded base64, or an RSA UKey user routed into the SM2 branch.

Understand the failure class

Related errors


AI-assisted analysis of jumpserver/jumpserver@6ec464fabd (2026-08-28). Data as JSON: /api/errors/558e9defdacdff76. Report an issue: GitHub.