parameters: introduce CertificateSigningRequest

Previously, CSRs were handled as a Str parameter which brought
trouble to Python 3 because of its more strict type requirements.
We introduce a CertificateSigningRequest parameter which allows to
use python-cryptography x509.CertificateSigningRequest to represent
CSRs in the framework.

https://pagure.io/freeipa/issue/7131
This commit is contained in:
Stanislav Laznicka
2017-09-22 14:52:36 +02:00
parent 26d721e6ea
commit 61605d28d8
7 changed files with 83 additions and 47 deletions

View File

@@ -115,12 +115,15 @@ from ipalib.base import check_name
from ipalib.plugable import ReadOnly, lock
from ipalib.errors import ConversionError, RequirementError, ValidationError
from ipalib.errors import (
PasswordMismatch, Base64DecodeError, CertificateFormatError
PasswordMismatch, Base64DecodeError, CertificateFormatError,
CertificateOperationError
)
from ipalib.constants import TYPE_ERROR, CALLABLE_ERROR, LDAP_GENERALIZED_TIME_FORMAT
from ipalib.text import Gettext, FixMe
from ipalib.util import json_serialize, validate_idna_domain
from ipalib.x509 import load_der_x509_certificate, IPACertificate
from ipalib.x509 import (
load_der_x509_certificate, IPACertificate, default_backend)
from ipalib.pkcs10 import strip_header as strip_csr_header
from ipapython import kerberos
from ipapython.dn import DN
from ipapython.dnsutil import DNSName
@@ -1452,6 +1455,60 @@ class Certificate(Param):
return super(Certificate, self)._convert_scalar(value)
class CertificateSigningRequest(Param):
type = crypto_x509.CertificateSigningRequest
type_error = _('must be a certificate signing request')
allowed_types = (crypto_x509.CertificateSigningRequest, bytes, unicode)
def __extract_der_from_input(self, value):
"""
Tries to get the DER representation of whatever we receive as an input
:param value:
bytes instance containing something we hope is a certificate
signing request
:returns:
base64-decoded representation of whatever we found in case input
had been something else than DER or something which resembles
DER, in which case we would just return input
"""
try:
value.decode('utf-8')
except UnicodeDecodeError:
# possibly DER-encoded CSR or something similar
return value
value = strip_csr_header(value)
return base64.b64decode(value)
def _convert_scalar(self, value, index=None):
"""
:param value:
either DER csr, base64-encoded csr or an object implementing the
cryptography.CertificateSigningRequest interface
:returns:
an object with the cryptography.CertificateSigningRequest interface
"""
if isinstance(value, unicode):
try:
value = value.encode('ascii')
except UnicodeDecodeError:
raise CertificateOperationError('not a valid CSR')
if isinstance(value, bytes):
# try to extract DER from whatever we got
value = self.__extract_der_from_input(value)
try:
value = crypto_x509.load_der_x509_csr(
value, backend=default_backend())
except ValueError as e:
raise CertificateOperationError(
error=_("Failure decoding Certificate Signing Request:"
" %s") % e)
return super(CertificateSigningRequest, self)._convert_scalar(value)
class Str(Data):
"""
A parameter for Unicode text (stored in the ``unicode`` type).

View File

@@ -29,13 +29,13 @@ def strip_header(csr):
Remove the header and footer (and surrounding material) from a CSR.
"""
headerlen = 40
s = csr.find("-----BEGIN NEW CERTIFICATE REQUEST-----")
s = csr.find(b"-----BEGIN NEW CERTIFICATE REQUEST-----")
if s == -1:
headerlen = 36
s = csr.find("-----BEGIN CERTIFICATE REQUEST-----")
s = csr.find(b"-----BEGIN CERTIFICATE REQUEST-----")
if s >= 0:
e = csr.find("-----END")
csr = csr[s+headerlen:e]
e = csr.find(b"-----END")
csr = csr[s + headerlen:e]
return csr

View File

@@ -197,6 +197,10 @@ def xml_wrap(value, version):
return base64.b64encode(
value.public_bytes(x509_Encoding.DER)).decode('ascii')
if isinstance(value, crypto_x509.CertificateSigningRequest):
return base64.b64encode(
value.public_bytes(x509_Encoding.DER)).decode('ascii')
assert type(value) in (unicode, float, bool, type(None)) + six.integer_types
return value
@@ -325,6 +329,7 @@ class _JSONPrimer(dict):
tuple: self._enc_list,
dict: self._enc_dict,
crypto_x509.Certificate: self._enc_certificate,
crypto_x509.CertificateSigningRequest: self._enc_certificate,
})
# int, long
for t in six.integer_types: