pgreco / rpms / ipa

Forked from forks/areguera/rpms/ipa 4 years ago
Clone
Blob Blame History Raw
This is a collection of an existing patch to remove csrgen for 4.7.1 and
additional patches that have been added for 4.7.90 pre1.

Additional reverted csrgen patches:

852618fd6529fbdd7b03077fae37c6fbbe45b51b
0ac1d3ea62efd9751fcc59cea46bcdafe1f11c37
7633d62d858c14523a99143aa0ff36f76bb4ff68
53f87ee5cd9d19f6fb91a9a1eafc8ea798095954
395a68d20887d0ac010e480e68b225d6dfeff726
03786ad9f3bd5edc351040847b8a49c9cd9288b2
c9d710a446d10aad72795e15bf041b87102628c1
2b90c8a20e45ade9bfd27731cccc94a34cf3f61e
61dde27f70b9f8dd1b57ad1fbc3744f3c380613a
806784dbd9e69a89c7a705c89bf42ba1fd4265c9
79378c90512a1cdd5f3d5ec6482e434caea06e01
bd5a5012d24820b54cdca2955f5405b84de1178c
26ab51ddf47f421f3404709052db89f08c05adaa
a53e17830c3d4fd59a62248d4447491675c6a80e
e7588ab2dc73e7f66ebc6cdcfb99470540e37731
136c6c3e2a4f77a27f435efd4a1cd95c9e089314
5420e9cfbe7803808b6e26d2dae64f2a6a50149a

Original patch from 4.7.1:

From 468bcf90cb985e2b1eb394bd752dc39aa4b75582 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Thu, 19 Jul 2018 18:37:18 -0400
Subject: [PATCH] Remove csrgen

This reverts commits:
* 72de679eb445c975ec70cd265d37d4927823ce5b
* 177f07e163d6d591a1e609d35e0a6f6f5347551e
* 80be18162921268be9c8981495c9e8a4de0c85cd
* 83e2c2b65eeb5a3aa4a59c0535e9177aac5e4637
* ada91c20588046bb147fc701718d3da4d2c080ca
* 4350dcdea22fd2284836315d0ae7d38733a7620e
* 39a5d9c5aae77687f67d9be02457733bdfb99ead
* a26cf0d7910dd4c0a4da08682b4be8d3d94ba520
* afd7c05d11432304bfdf183832a21d419f363689
* f1a1c6eca1b294f24174d7b0e1f78de46d9d5b05
* fc58eff6a3d7fe805e612b8b002304d8b9cd4ba9
* 10ef5947860f5098182b1f95c08c1158e2da15f9

https://bugzilla.redhat.com/show_bug.cgi?id=1432630
---
 freeipa.spec.in                                    |  14 -
 ipaclient/csrgen.py                                | 488 ---------------------
 ipaclient/csrgen/profiles/caIPAserviceCert.json    |  15 -
 ipaclient/csrgen/profiles/userCert.json            |  15 -
 ipaclient/csrgen/rules/dataDNS.json                |   8 -
 ipaclient/csrgen/rules/dataEmail.json              |   8 -
 ipaclient/csrgen/rules/dataHostCN.json             |   8 -
 ipaclient/csrgen/rules/dataSubjectBase.json        |   8 -
 ipaclient/csrgen/rules/dataUsernameCN.json         |   8 -
 ipaclient/csrgen/rules/syntaxSAN.json              |   8 -
 ipaclient/csrgen/rules/syntaxSubject.json          |   9 -
 ipaclient/csrgen/templates/openssl_base.tmpl       |  17 -
 ipaclient/csrgen/templates/openssl_macros.tmpl     |  29 --
 ipaclient/csrgen_ffi.py                            | 331 --------------
 ipaclient/plugins/cert.py                          |  80 ----
 ipaclient/plugins/csrgen.py                        | 128 ------
 ipaclient/setup.py                                 |   8 -
 .../data/test_csrgen/configs/caIPAserviceCert.conf |  16 -
 .../data/test_csrgen/configs/userCert.conf         |  16 -
 .../data/test_csrgen/profiles/profile.json         |   8 -
 .../data/test_csrgen/rules/basic.json              |   5 -
 .../data/test_csrgen/rules/options.json            |   8 -
 .../data/test_csrgen/templates/identity_base.tmpl  |   1 -
 ipatests/test_ipaclient/test_csrgen.py             | 304 -------------
 24 files changed, 1540 deletions(-)
 delete mode 100644 ipaclient/csrgen.py
 delete mode 100644 ipaclient/csrgen/profiles/caIPAserviceCert.json
 delete mode 100644 ipaclient/csrgen/profiles/userCert.json
 delete mode 100644 ipaclient/csrgen/rules/dataDNS.json
 delete mode 100644 ipaclient/csrgen/rules/dataEmail.json
 delete mode 100644 ipaclient/csrgen/rules/dataHostCN.json
 delete mode 100644 ipaclient/csrgen/rules/dataSubjectBase.json
 delete mode 100644 ipaclient/csrgen/rules/dataUsernameCN.json
 delete mode 100644 ipaclient/csrgen/rules/syntaxSAN.json
 delete mode 100644 ipaclient/csrgen/rules/syntaxSubject.json
 delete mode 100644 ipaclient/csrgen/templates/openssl_base.tmpl
 delete mode 100644 ipaclient/csrgen/templates/openssl_macros.tmpl
 delete mode 100644 ipaclient/csrgen_ffi.py
 delete mode 100644 ipaclient/plugins/csrgen.py
 delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/configs/caIPAserviceCert.conf
 delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/configs/userCert.conf
 delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/profiles/profile.json
 delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/rules/basic.json
 delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/rules/options.json
 delete mode 100644 ipatests/test_ipaclient/data/test_csrgen/templates/identity_base.tmpl
 delete mode 100644 ipatests/test_ipaclient/test_csrgen.py

diff -urN freeipa-4.7.90.pre1.orig/freeipa.spec.in freeipa-4.7.90.pre1/freeipa.spec.in
--- freeipa-4.7.90.pre1.orig/freeipa.spec.in	2019-04-29 08:28:24.722860593 +0200
+++ freeipa-4.7.90.pre1/freeipa.spec.in	2019-05-06 18:31:26.443792711 +0200
@@ -1225,13 +1225,6 @@
 %dir %{python3_sitelib}/ipaclient/remote_plugins/2_*
 %{python3_sitelib}/ipaclient/remote_plugins/2_*/*.py
 %{python3_sitelib}/ipaclient/remote_plugins/2_*/__pycache__/*.py*
-%dir %{python3_sitelib}/ipaclient/csrgen
-%dir %{python3_sitelib}/ipaclient/csrgen/profiles
-%{python3_sitelib}/ipaclient/csrgen/profiles/*.json
-%dir %{python3_sitelib}/ipaclient/csrgen/rules
-%{python3_sitelib}/ipaclient/csrgen/rules/*.json
-%dir %{python3_sitelib}/ipaclient/csrgen/templates
-%{python3_sitelib}/ipaclient/csrgen/templates/*.tmpl
 %{python3_sitelib}/ipaclient-*.egg-info
 
 
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/csrgen/profiles/caIPAserviceCert.json freeipa-4.7.90.pre1/ipaclient/csrgen/profiles/caIPAserviceCert.json
--- freeipa-4.7.90.pre1.orig/ipaclient/csrgen/profiles/caIPAserviceCert.json	2019-04-29 17:06:41.408224320 +0200
+++ freeipa-4.7.90.pre1/ipaclient/csrgen/profiles/caIPAserviceCert.json	1970-01-01 01:00:00.000000000 +0100
@@ -1,15 +0,0 @@
-[
-    {
-        "syntax": "syntaxSubject",
-        "data": [
-            "dataHostCN",
-            "dataSubjectBase"
-        ]
-    },
-    {
-        "syntax": "syntaxSAN",
-        "data": [
-            "dataDNS"
-        ]
-    }
-]
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/csrgen/profiles/userCert.json freeipa-4.7.90.pre1/ipaclient/csrgen/profiles/userCert.json
--- freeipa-4.7.90.pre1.orig/ipaclient/csrgen/profiles/userCert.json	2019-04-29 17:06:41.417224194 +0200
+++ freeipa-4.7.90.pre1/ipaclient/csrgen/profiles/userCert.json	1970-01-01 01:00:00.000000000 +0100
@@ -1,15 +0,0 @@
-[
-    {
-        "syntax": "syntaxSubject",
-        "data": [
-            "dataUsernameCN",
-            "dataSubjectBase"
-        ]
-    },
-    {
-        "syntax": "syntaxSAN",
-        "data": [
-            "dataEmail"
-        ]
-    }
-]
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/csrgen/rules/dataDNS.json freeipa-4.7.90.pre1/ipaclient/csrgen/rules/dataDNS.json
--- freeipa-4.7.90.pre1.orig/ipaclient/csrgen/rules/dataDNS.json	2019-04-29 17:06:41.422224125 +0200
+++ freeipa-4.7.90.pre1/ipaclient/csrgen/rules/dataDNS.json	1970-01-01 01:00:00.000000000 +0100
@@ -1,8 +0,0 @@
-{
-  "rule": {
-    "template": "DNS = {{subject.krbprincipalname.0.partition('/')[2].partition('@')[0]}}"
-  },
-  "options": {
-    "data_source": "subject.krbprincipalname.0.partition('/')[2].partition('@')[0]"
-  }
-}
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/csrgen/rules/dataEmail.json freeipa-4.7.90.pre1/ipaclient/csrgen/rules/dataEmail.json
--- freeipa-4.7.90.pre1.orig/ipaclient/csrgen/rules/dataEmail.json	2019-04-29 17:06:41.426224069 +0200
+++ freeipa-4.7.90.pre1/ipaclient/csrgen/rules/dataEmail.json	1970-01-01 01:00:00.000000000 +0100
@@ -1,8 +0,0 @@
-{
-  "rule": {
-    "template": "email = {{subject.mail.0}}"
-  },
-  "options": {
-    "data_source": "subject.mail.0"
-  }
-}
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/csrgen/rules/dataHostCN.json freeipa-4.7.90.pre1/ipaclient/csrgen/rules/dataHostCN.json
--- freeipa-4.7.90.pre1.orig/ipaclient/csrgen/rules/dataHostCN.json	2019-04-29 17:06:41.430224013 +0200
+++ freeipa-4.7.90.pre1/ipaclient/csrgen/rules/dataHostCN.json	1970-01-01 01:00:00.000000000 +0100
@@ -1,8 +0,0 @@
-{
-  "rule": {
-    "template": "CN={{subject.krbprincipalname.0.partition('/')[2].partition('@')[0]}}"
-  },
-  "options": {
-    "data_source": "subject.krbprincipalname.0.partition('/')[2].partition('@')[0]"
-  }
-}
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/csrgen/rules/dataSubjectBase.json freeipa-4.7.90.pre1/ipaclient/csrgen/rules/dataSubjectBase.json
--- freeipa-4.7.90.pre1.orig/ipaclient/csrgen/rules/dataSubjectBase.json	2019-04-29 17:06:41.437223916 +0200
+++ freeipa-4.7.90.pre1/ipaclient/csrgen/rules/dataSubjectBase.json	1970-01-01 01:00:00.000000000 +0100
@@ -1,8 +0,0 @@
-{
-  "rule": {
-    "template": "{{config.ipacertificatesubjectbase.0}}"
-  },
-  "options": {
-    "data_source": "config.ipacertificatesubjectbase.0"
-  }
-}
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/csrgen/rules/dataUsernameCN.json freeipa-4.7.90.pre1/ipaclient/csrgen/rules/dataUsernameCN.json
--- freeipa-4.7.90.pre1.orig/ipaclient/csrgen/rules/dataUsernameCN.json	2019-04-29 17:06:41.449223748 +0200
+++ freeipa-4.7.90.pre1/ipaclient/csrgen/rules/dataUsernameCN.json	1970-01-01 01:00:00.000000000 +0100
@@ -1,8 +0,0 @@
-{
-  "rule": {
-    "template": "CN={{subject.uid.0}}"
-  },
-  "options": {
-    "data_source": "subject.uid.0"
-  }
-}
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/csrgen/rules/syntaxSAN.json freeipa-4.7.90.pre1/ipaclient/csrgen/rules/syntaxSAN.json
--- freeipa-4.7.90.pre1.orig/ipaclient/csrgen/rules/syntaxSAN.json	2019-04-29 17:06:41.456223650 +0200
+++ freeipa-4.7.90.pre1/ipaclient/csrgen/rules/syntaxSAN.json	1970-01-01 01:00:00.000000000 +0100
@@ -1,8 +0,0 @@
-{
-  "rule": {
-    "template": "subjectAltName = @{% call openssl.section() %}{{ datarules|join('\n') }}{% endcall %}"
-  },
-  "options": {
-    "extension": true
-  }
-}
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/csrgen/rules/syntaxSubject.json freeipa-4.7.90.pre1/ipaclient/csrgen/rules/syntaxSubject.json
--- freeipa-4.7.90.pre1.orig/ipaclient/csrgen/rules/syntaxSubject.json	2019-04-29 17:06:41.461223581 +0200
+++ freeipa-4.7.90.pre1/ipaclient/csrgen/rules/syntaxSubject.json	1970-01-01 01:00:00.000000000 +0100
@@ -1,9 +0,0 @@
-{
-  "rule": {
-    "template": "distinguished_name = {% call openssl.section() %}{{ datarules|reverse|join('\n') }}{% endcall %}"
-  },
-  "options": {
-    "required": true,
-    "data_source_combinator": "and"
-  }
-}
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/csrgen/templates/openssl_base.tmpl freeipa-4.7.90.pre1/ipaclient/csrgen/templates/openssl_base.tmpl
--- freeipa-4.7.90.pre1.orig/ipaclient/csrgen/templates/openssl_base.tmpl	2019-04-29 17:06:41.469223469 +0200
+++ freeipa-4.7.90.pre1/ipaclient/csrgen/templates/openssl_base.tmpl	1970-01-01 01:00:00.000000000 +0100
@@ -1,17 +0,0 @@
-{% raw -%}
-{% import "openssl_macros.tmpl" as openssl -%}
-{% endraw -%}
-[ req ]
-prompt = no
-encrypt_key = no
-
-{{ parameters|join('\n') }}
-{% raw %}{% set rendered_extensions -%}{% endraw %}
-{{ extensions|join('\n') }}
-{% raw -%}
-{%- endset -%}
-{% if rendered_extensions -%}
-req_extensions = {% call openssl.section() %}{{ rendered_extensions }}{% endcall %}
-{% endif %}
-{{ openssl.openssl_sections|join('\n\n') }}
-{%- endraw %}
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/csrgen/templates/openssl_macros.tmpl freeipa-4.7.90.pre1/ipaclient/csrgen/templates/openssl_macros.tmpl
--- freeipa-4.7.90.pre1.orig/ipaclient/csrgen/templates/openssl_macros.tmpl	2019-04-29 17:06:41.475223385 +0200
+++ freeipa-4.7.90.pre1/ipaclient/csrgen/templates/openssl_macros.tmpl	1970-01-01 01:00:00.000000000 +0100
@@ -1,29 +0,0 @@
-{# List containing rendered sections to be included at end #}
-{% set openssl_sections = [] %}
-
-{#
-List containing one entry for each section name allocated. Because of
-scoping rules, we need to use a list so that it can be a "per-render global"
-that gets updated in place. Real globals are shared by all templates with the
-same environment, and variables defined in the macro don't persist after the
-macro invocation ends.
-#}
-{% set openssl_section_num = [] %}
-
-{% macro section() -%}
-{% set name -%}
-sec{{ openssl_section_num|length -}}
-{% endset -%}
-{% do openssl_section_num.append('') -%}
-{% set contents %}{{ caller() }}{% endset -%}
-{% if contents -%}
-{% set sectiondata = formatsection(name, contents) -%}
-{% do openssl_sections.append(sectiondata) -%}
-{% endif -%}
-{{ name -}}
-{% endmacro %}
-
-{% macro formatsection(name, contents) -%}
-[ {{ name }} ]
-{{ contents -}}
-{% endmacro %}
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/csrgen_ffi.py freeipa-4.7.90.pre1/ipaclient/csrgen_ffi.py
--- freeipa-4.7.90.pre1.orig/ipaclient/csrgen_ffi.py	2019-04-29 17:06:41.367224892 +0200
+++ freeipa-4.7.90.pre1/ipaclient/csrgen_ffi.py	1970-01-01 01:00:00.000000000 +0100
@@ -1,331 +0,0 @@
-from cffi import FFI
-import ctypes.util
-
-from ipalib import errors
-
-_ffi = FFI()
-
-_ffi.cdef('''
-typedef ... CONF;
-typedef ... CONF_METHOD;
-typedef ... BIO;
-typedef ... ipa_STACK_OF_CONF_VALUE;
-
-/* openssl/conf.h */
-typedef struct {
-    char *section;
-    char *name;
-    char *value;
-} CONF_VALUE;
-
-CONF *NCONF_new(CONF_METHOD *meth);
-void NCONF_free(CONF *conf);
-int NCONF_load_bio(CONF *conf, BIO *bp, long *eline);
-ipa_STACK_OF_CONF_VALUE *NCONF_get_section(const CONF *conf,
-                                        const char *section);
-char *NCONF_get_string(const CONF *conf, const char *group, const char *name);
-
-/* openssl/safestack.h */
-// int sk_CONF_VALUE_num(ipa_STACK_OF_CONF_VALUE *);
-// CONF_VALUE *sk_CONF_VALUE_value(ipa_STACK_OF_CONF_VALUE *, int);
-
-/* openssl/stack.h */
-typedef ... _STACK;
-
-int OPENSSL_sk_num(const _STACK *);
-void *OPENSSL_sk_value(const _STACK *, int);
-
-int sk_num(const _STACK *);
-void *sk_value(const _STACK *, int);
-
-/* openssl/bio.h */
-BIO *BIO_new_mem_buf(const void *buf, int len);
-int BIO_free(BIO *a);
-
-/* openssl/asn1.h */
-typedef struct ASN1_ENCODING_st {
-    unsigned char *enc;         /* DER encoding */
-    long len;                   /* Length of encoding */
-    int modified;               /* set to 1 if 'enc' is invalid */
-} ASN1_ENCODING;
-
-/* openssl/evp.h */
-typedef ... EVP_PKEY;
-
-void EVP_PKEY_free(EVP_PKEY *pkey);
-
-/* openssl/x509.h */
-typedef ... ASN1_INTEGER;
-typedef ... ASN1_BIT_STRING;
-typedef ... ASN1_OBJECT;
-typedef ... X509;
-typedef ... X509_ALGOR;
-typedef ... X509_CRL;
-typedef ... X509_NAME;
-typedef ... X509_PUBKEY;
-typedef ... ipa_STACK_OF_X509_ATTRIBUTE;
-
-typedef struct X509_req_info_st {
-    ASN1_ENCODING enc;
-    ASN1_INTEGER *version;
-    X509_NAME *subject;
-    X509_PUBKEY *pubkey;
-    /*  d=2 hl=2 l=  0 cons: cont: 00 */
-    ipa_STACK_OF_X509_ATTRIBUTE *attributes; /* [ 0 ] */
-} X509_REQ_INFO;
-
-typedef struct X509_req_st {
-    X509_REQ_INFO *req_info;
-    X509_ALGOR *sig_alg;
-    ASN1_BIT_STRING *signature;
-    int references;
-} X509_REQ;
-
-X509_REQ *X509_REQ_new(void);
-void X509_REQ_free(X509_REQ *);
-EVP_PKEY *d2i_PUBKEY_bio(BIO *bp, EVP_PKEY **a);
-int X509_REQ_set_pubkey(X509_REQ *x, EVP_PKEY *pkey);
-int X509_NAME_add_entry_by_OBJ(X509_NAME *name, const ASN1_OBJECT *obj, int type,
-                               const unsigned char *bytes, int len, int loc,
-                               int set);
-int X509_NAME_entry_count(X509_NAME *name);
-int i2d_X509_REQ_INFO(X509_REQ_INFO *a, unsigned char **out);
-
-/* openssl/objects.h */
-ASN1_OBJECT *OBJ_txt2obj(const char *s, int no_name);
-
-/* openssl/x509v3.h */
-typedef ... X509V3_CONF_METHOD;
-
-typedef struct v3_ext_ctx {
-    int flags;
-    X509 *issuer_cert;
-    X509 *subject_cert;
-    X509_REQ *subject_req;
-    X509_CRL *crl;
-    X509V3_CONF_METHOD *db_meth;
-    void *db;
-} X509V3_CTX;
-
-void X509V3_set_ctx(X509V3_CTX *ctx, X509 *issuer, X509 *subject,
-                    X509_REQ *req, X509_CRL *crl, int flags);
-void X509V3_set_nconf(X509V3_CTX *ctx, CONF *conf);
-int X509V3_EXT_REQ_add_nconf(CONF *conf, X509V3_CTX *ctx, char *section,
-                             X509_REQ *req);
-
-/* openssl/x509v3.h */
-unsigned long ERR_get_error(void);
-char *ERR_error_string(unsigned long e, char *buf);
-''')  # noqa: E501
-
-_libcrypto = _ffi.dlopen(ctypes.util.find_library('crypto'))
-
-NULL = _ffi.NULL
-
-# openssl/conf.h
-NCONF_new = _libcrypto.NCONF_new
-NCONF_free = _libcrypto.NCONF_free
-NCONF_load_bio = _libcrypto.NCONF_load_bio
-NCONF_get_section = _libcrypto.NCONF_get_section
-NCONF_get_string = _libcrypto.NCONF_get_string
-
-# openssl/stack.h
-try:
-    sk_num = _libcrypto.OPENSSL_sk_num
-    sk_value = _libcrypto.OPENSSL_sk_value
-except AttributeError:
-    sk_num = _libcrypto.sk_num
-    sk_value = _libcrypto.sk_value
-
-
-def sk_CONF_VALUE_num(sk):
-    return sk_num(_ffi.cast("_STACK *", sk))
-
-
-def sk_CONF_VALUE_value(sk, i):
-    return _ffi.cast("CONF_VALUE *", sk_value(_ffi.cast("_STACK *", sk), i))
-
-
-# openssl/bio.h
-BIO_new_mem_buf = _libcrypto.BIO_new_mem_buf
-BIO_free = _libcrypto.BIO_free
-
-# openssl/x509.h
-X509_REQ_new = _libcrypto.X509_REQ_new
-X509_REQ_free = _libcrypto.X509_REQ_free
-X509_REQ_set_pubkey = _libcrypto.X509_REQ_set_pubkey
-d2i_PUBKEY_bio = _libcrypto.d2i_PUBKEY_bio
-i2d_X509_REQ_INFO = _libcrypto.i2d_X509_REQ_INFO
-X509_NAME_add_entry_by_OBJ = _libcrypto.X509_NAME_add_entry_by_OBJ
-X509_NAME_entry_count = _libcrypto.X509_NAME_entry_count
-
-
-def X509_REQ_get_subject_name(req):
-    return req.req_info.subject
-
-
-# openssl/objects.h
-OBJ_txt2obj = _libcrypto.OBJ_txt2obj
-
-# openssl/evp.h
-EVP_PKEY_free = _libcrypto.EVP_PKEY_free
-
-# openssl/asn1.h
-MBSTRING_UTF8 = 0x1000
-
-# openssl/x509v3.h
-X509V3_set_ctx = _libcrypto.X509V3_set_ctx
-X509V3_set_nconf = _libcrypto.X509V3_set_nconf
-X509V3_EXT_REQ_add_nconf = _libcrypto.X509V3_EXT_REQ_add_nconf
-
-# openssl/err.h
-ERR_get_error = _libcrypto.ERR_get_error
-ERR_error_string = _libcrypto.ERR_error_string
-
-
-def _raise_openssl_errors():
-    msgs = []
-
-    code = ERR_get_error()
-    while code != 0:
-        msg = _ffi.string(ERR_error_string(code, NULL))
-        try:
-            strmsg = msg.decode('utf-8')
-        except UnicodeDecodeError:
-            strmsg = repr(msg)
-        msgs.append(strmsg)
-        code = ERR_get_error()
-
-    raise errors.CSRTemplateError(reason='\n'.join(msgs))
-
-
-def _parse_dn_section(subj, dn_sk):
-    for i in range(sk_CONF_VALUE_num(dn_sk)):
-        v = sk_CONF_VALUE_value(dn_sk, i)
-        rdn_type = _ffi.string(v.name)
-
-        # Skip past any leading X. X: X, etc to allow for multiple instances
-        for idx, c in enumerate(rdn_type):
-            if c in b':,.':
-                if idx+1 < len(rdn_type):
-                    rdn_type = rdn_type[idx+1:]
-                break
-        if rdn_type.startswith(b'+'):
-            rdn_type = rdn_type[1:]
-            mval = -1
-        else:
-            mval = 0
-
-        # convert rdn_type to an OID
-        #
-        # OpenSSL is fussy about the case of the string.  For example,
-        # lower-case 'o' (for "organization name") is not recognised.
-        # Therefore, try to convert the given string into an OID.  If
-        # that fails, convert it upper case and try again.
-        #
-        oid = OBJ_txt2obj(rdn_type, 0)
-        if oid == NULL:
-            oid = OBJ_txt2obj(rdn_type.upper(), 0)
-        if oid == NULL:
-            raise errors.CSRTemplateError(
-                reason='unrecognised attribute type: {}'
-                .format(rdn_type.decode('utf-8')))
-
-        if not X509_NAME_add_entry_by_OBJ(
-                subj, oid, MBSTRING_UTF8,
-                _ffi.cast("unsigned char *", v.value), -1, -1, mval):
-            _raise_openssl_errors()
-
-    if not X509_NAME_entry_count(subj):
-        raise errors.CSRTemplateError(
-            reason='error, subject in config file is empty')
-
-
-def build_requestinfo(config, public_key_info):
-    '''
-    Return a cffi buffer containing a DER-encoded CertificationRequestInfo.
-
-    The returned object implements the buffer protocol.
-
-    '''
-    reqdata = NULL
-    req = NULL
-    nconf_bio = NULL
-    pubkey_bio = NULL
-    pubkey = NULL
-
-    try:
-        reqdata = NCONF_new(NULL)
-        if reqdata == NULL:
-            _raise_openssl_errors()
-
-        nconf_bio = BIO_new_mem_buf(config, len(config))
-        errorline = _ffi.new('long[1]', [-1])
-        i = NCONF_load_bio(reqdata, nconf_bio, errorline)
-        if i < 0:
-            if errorline[0] < 0:
-                raise errors.CSRTemplateError(reason="Can't load config file")
-            else:
-                raise errors.CSRTemplateError(
-                    reason='Error on line %d of config file' % errorline[0])
-
-        dn_sect = NCONF_get_string(reqdata, b'req', b'distinguished_name')
-        if dn_sect == NULL:
-            raise errors.CSRTemplateError(
-                reason='Unable to find "distinguished_name" key in config')
-
-        dn_sk = NCONF_get_section(reqdata, dn_sect)
-        if dn_sk == NULL:
-            raise errors.CSRTemplateError(
-                reason='Unable to find "%s" section in config' %
-                _ffi.string(dn_sect))
-
-        pubkey_bio = BIO_new_mem_buf(public_key_info, len(public_key_info))
-        pubkey = d2i_PUBKEY_bio(pubkey_bio, NULL)
-        if pubkey == NULL:
-            _raise_openssl_errors()
-
-        req = X509_REQ_new()
-        if req == NULL:
-            _raise_openssl_errors()
-
-        subject = X509_REQ_get_subject_name(req)
-
-        _parse_dn_section(subject, dn_sk)
-
-        if not X509_REQ_set_pubkey(req, pubkey):
-            _raise_openssl_errors()
-
-        ext_ctx = _ffi.new("X509V3_CTX[1]")
-        X509V3_set_ctx(ext_ctx, NULL, NULL, req, NULL, 0)
-        X509V3_set_nconf(ext_ctx, reqdata)
-
-        extn_section = NCONF_get_string(reqdata, b"req", b"req_extensions")
-        if extn_section != NULL:
-            if not X509V3_EXT_REQ_add_nconf(
-                    reqdata, ext_ctx, extn_section, req):
-                _raise_openssl_errors()
-
-        der_len = i2d_X509_REQ_INFO(req.req_info, NULL)
-        if der_len < 0:
-            _raise_openssl_errors()
-
-        der_buf = _ffi.new("unsigned char[%d]" % der_len)
-        der_out = _ffi.new("unsigned char **", der_buf)
-        der_len = i2d_X509_REQ_INFO(req.req_info, der_out)
-        if der_len < 0:
-            _raise_openssl_errors()
-
-        return _ffi.buffer(der_buf, der_len)
-
-    finally:
-        if reqdata != NULL:
-            NCONF_free(reqdata)
-        if req != NULL:
-            X509_REQ_free(req)
-        if nconf_bio != NULL:
-            BIO_free(nconf_bio)
-        if pubkey_bio != NULL:
-            BIO_free(pubkey_bio)
-        if pubkey != NULL:
-            EVP_PKEY_free(pubkey)
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/csrgen.py freeipa-4.7.90.pre1/ipaclient/csrgen.py
--- freeipa-4.7.90.pre1.orig/ipaclient/csrgen.py	2019-04-29 17:06:41.360224990 +0200
+++ freeipa-4.7.90.pre1/ipaclient/csrgen.py	1970-01-01 01:00:00.000000000 +0100
@@ -1,488 +0,0 @@
-#
-# Copyright (C) 2016  FreeIPA Contributors see COPYING for license
-#
-
-import base64
-import collections
-import errno
-import json
-import logging
-import os
-import os.path
-import pipes
-import subprocess
-import traceback
-import codecs
-
-import pkg_resources
-
-from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives.asymmetric import padding
-from cryptography.hazmat.primitives import hashes
-from cryptography.hazmat.primitives.serialization import (
-    load_pem_private_key, Encoding, PublicFormat)
-from cryptography.x509 import load_pem_x509_certificate
-import jinja2
-import jinja2.ext
-import jinja2.sandbox
-from pyasn1.codec.der import decoder, encoder
-from pyasn1.type import univ
-from pyasn1_modules import rfc2314
-import six
-
-from ipalib import api
-from ipalib import errors
-from ipalib.text import _
-
-if six.PY3:
-    unicode = str
-
-__doc__ = _("""
-Routines for constructing certificate signing requests using IPA data and
-stored templates.
-""")
-
-logger = logging.getLogger(__name__)
-
-
-class IndexableUndefined(jinja2.Undefined):
-    def __getitem__(self, key):
-        return jinja2.Undefined(
-            hint=self._undefined_hint, obj=self._undefined_obj,
-            name=self._undefined_name, exc=self._undefined_exception)
-
-
-class IPAExtension(jinja2.ext.Extension):
-    """Jinja2 extension providing useful features for CSR generation rules."""
-
-    def __init__(self, environment):
-        super(IPAExtension, self).__init__(environment)
-
-        environment.filters.update(
-            quote=self.quote,
-            required=self.required,
-        )
-
-    def quote(self, data):
-        return pipes.quote(data)
-
-    def required(self, data, name):
-        if not data:
-            raise errors.CSRTemplateError(
-                reason=_(
-                    'Required CSR generation rule %(name)s is missing data') %
-                {'name': name})
-        return data
-
-
-class Formatter:
-    """
-    Class for processing a set of CSR generation rules into a template.
-
-    The template can be rendered with user and database data to produce a
-    config, which specifies how to build a CSR.
-
-    Subclasses of Formatter should set the value of base_template_name to the
-    filename of a base template with spaces for the processed rules.
-    Additionally, they should override the _get_template_params method to
-    produce the correct output for the base template.
-    """
-    base_template_name = None
-
-    def __init__(self, csr_data_dir=None):
-        # chain loaders:
-        # 1) csr_data_dir/templates
-        # 2) /etc/ipa/csrgen/templates
-        # 3) ipaclient/csrgen/templates
-        loaders = []
-        if csr_data_dir is not None:
-            loaders.append(jinja2.FileSystemLoader(
-                os.path.join(csr_data_dir, 'templates'))
-            )
-        loaders.append(jinja2.FileSystemLoader(
-            os.path.join(api.env.confdir, 'csrgen/templates'))
-        )
-        loaders.append(jinja2.PackageLoader('ipaclient', 'csrgen/templates'))
-
-        self.jinja2 = jinja2.sandbox.SandboxedEnvironment(
-            loader=jinja2.ChoiceLoader(loaders),
-            extensions=[jinja2.ext.ExprStmtExtension, IPAExtension],
-            keep_trailing_newline=True, undefined=IndexableUndefined)
-
-        self.passthrough_globals = {}
-
-    def _define_passthrough(self, call):
-        """Some macros are meant to be interpreted during the final render, not
-        when data rules are interpolated into syntax rules. This method allows
-        those macros to be registered so that calls to them are passed through
-        to the prepared rule rather than interpreted.
-        """
-
-        def passthrough(caller):
-            return u'{%% call %s() %%}%s{%% endcall %%}' % (call, caller())
-
-        parts = call.split('.')
-        current_level = self.passthrough_globals
-        for part in parts[:-1]:
-            if part not in current_level:
-                current_level[part] = {}
-            current_level = current_level[part]
-        current_level[parts[-1]] = passthrough
-
-    def build_template(self, rules):
-        """
-        Construct a template that can produce CSR generator strings.
-
-        :param rules: list of FieldMapping to use to populate the template.
-
-        :returns: jinja2.Template that can be rendered to produce the CSR data.
-        """
-        syntax_rules = []
-        for field_mapping in rules:
-            data_rules_prepared = [
-                self._prepare_data_rule(rule)
-                for rule in field_mapping.data_rules]
-
-            data_sources = []
-            for xrule in field_mapping.data_rules:
-                data_source = xrule.options.get('data_source')
-                if data_source:
-                    data_sources.append(data_source)
-
-            syntax_rules.append(self._prepare_syntax_rule(
-                field_mapping.syntax_rule, data_rules_prepared,
-                field_mapping.description, data_sources))
-
-        template_params = self._get_template_params(syntax_rules)
-        base_template = self.jinja2.get_template(
-            self.base_template_name, globals=self.passthrough_globals)
-
-        try:
-            combined_template_source = base_template.render(**template_params)
-        except jinja2.UndefinedError:
-            logger.debug(traceback.format_exc())
-            raise errors.CSRTemplateError(reason=_(
-                'Template error when formatting certificate data'))
-
-        logger.debug(
-            'Formatting with template: %s', combined_template_source)
-        combined_template = self.jinja2.from_string(combined_template_source)
-
-        return combined_template
-
-    def _wrap_conditional(self, rule, condition):
-        rule = '{%% if %s %%}%s{%% endif %%}' % (condition, rule)
-        return rule
-
-    def _wrap_required(self, rule, description):
-        template = '{%% filter required("%s") %%}%s{%% endfilter %%}' % (
-            description, rule)
-
-        return template
-
-    def _prepare_data_rule(self, data_rule):
-        template = data_rule.template
-
-        data_source = data_rule.options.get('data_source')
-        if data_source:
-            template = self._wrap_conditional(template, data_source)
-
-        return template
-
-    def _prepare_syntax_rule(
-            self, syntax_rule, data_rules, description, data_sources):
-        logger.debug('Syntax rule template: %s', syntax_rule.template)
-        template = self.jinja2.from_string(
-            syntax_rule.template, globals=self.passthrough_globals)
-        is_required = syntax_rule.options.get('required', False)
-        try:
-            prepared_template = template.render(datarules=data_rules)
-        except jinja2.UndefinedError:
-            logger.debug(traceback.format_exc())
-            raise errors.CSRTemplateError(reason=_(
-                'Template error when formatting certificate data'))
-
-        if data_sources:
-            combinator = ' %s ' % syntax_rule.options.get(
-                'data_source_combinator', 'or')
-            condition = combinator.join(data_sources)
-            prepared_template = self._wrap_conditional(
-                prepared_template, condition)
-
-        if is_required:
-            prepared_template = self._wrap_required(
-                prepared_template, description)
-
-        return prepared_template
-
-    def _get_template_params(self, syntax_rules):
-        """
-        Package the syntax rules into fields expected by the base template.
-
-        :param syntax_rules: list of prepared syntax rules to be included in
-            the template.
-
-        :returns: dict of values needed to render the base template.
-        """
-        raise NotImplementedError('Formatter class must be subclassed')
-
-
-class OpenSSLFormatter(Formatter):
-    """Formatter class generating the openssl config-file format."""
-
-    base_template_name = 'openssl_base.tmpl'
-
-    # Syntax rules are wrapped in this data structure, to keep track of whether
-    # each goes in the extension or the root section
-    SyntaxRule = collections.namedtuple(
-        'SyntaxRule', ['template', 'is_extension'])
-
-    def __init__(self, *args, **kwargs):
-        super(OpenSSLFormatter, self).__init__(*args, **kwargs)
-        self._define_passthrough('openssl.section')
-
-    def _get_template_params(self, syntax_rules):
-        parameters = [rule.template for rule in syntax_rules
-                      if not rule.is_extension]
-        extensions = [rule.template for rule in syntax_rules
-                      if rule.is_extension]
-
-        return {'parameters': parameters, 'extensions': extensions}
-
-    def _prepare_syntax_rule(
-            self, syntax_rule, data_rules, description, data_sources):
-        """Overrides method to pull out whether rule is an extension or not."""
-        prepared_template = super(OpenSSLFormatter, self)._prepare_syntax_rule(
-            syntax_rule, data_rules, description, data_sources)
-        is_extension = syntax_rule.options.get('extension', False)
-        return self.SyntaxRule(prepared_template, is_extension)
-
-
-class FieldMapping:
-    """Representation of the rules needed to construct a complete cert field.
-
-    Attributes:
-        description: str, a name or description of this field, to be used in
-            messages
-        syntax_rule: Rule, the rule defining the syntax of this field
-        data_rules: list of Rule, the rules that produce data to be stored in
-            this field
-    """
-    __slots__ = ['description', 'syntax_rule', 'data_rules']
-
-    def __init__(self, description, syntax_rule, data_rules):
-        self.description = description
-        self.syntax_rule = syntax_rule
-        self.data_rules = data_rules
-
-
-class Rule:
-    __slots__ = ['name', 'template', 'options']
-
-    def __init__(self, name, template, options):
-        self.name = name
-        self.template = template
-        self.options = options
-
-
-class RuleProvider:
-    def rules_for_profile(self, profile_id):
-        """
-        Return the rules needed to build a CSR using the given profile.
-
-        :param profile_id: str, name of the CSR generation profile to use
-
-        :returns: list of FieldMapping, filled out with the appropriate rules
-        """
-        raise NotImplementedError('RuleProvider class must be subclassed')
-
-
-class FileRuleProvider(RuleProvider):
-    def __init__(self, csr_data_dir=None):
-        self.rules = {}
-        self._csrgen_data_dirs = []
-        if csr_data_dir is not None:
-            self._csrgen_data_dirs.append(csr_data_dir)
-        self._csrgen_data_dirs.append(
-            os.path.join(api.env.confdir, 'csrgen')
-        )
-        self._csrgen_data_dirs.append(
-            pkg_resources.resource_filename('ipaclient', 'csrgen')
-        )
-
-    def _open(self, subdir, filename):
-        for data_dir in self._csrgen_data_dirs:
-            path = os.path.join(data_dir, subdir, filename)
-            try:
-                return open(path)
-            except IOError as e:
-                if e.errno != errno.ENOENT:
-                    raise
-        raise IOError(
-            errno.ENOENT,
-            "'{}' not found in {}".format(
-                os.path.join(subdir, filename),
-                ", ".join(self._csrgen_data_dirs)
-            )
-        )
-
-    def _rule(self, rule_name):
-        if rule_name not in self.rules:
-            try:
-                with self._open('rules', '%s.json' % rule_name) as f:
-                    ruleconf = json.load(f)
-            except IOError:
-                raise errors.NotFound(
-                    reason=_('No generation rule %(rulename)s found.') %
-                    {'rulename': rule_name})
-
-            try:
-                rule = ruleconf['rule']
-            except KeyError:
-                raise errors.EmptyResult(
-                    reason=_('Generation rule "%(rulename)s" is missing the'
-                             ' "rule" key') % {'rulename': rule_name})
-
-            options = ruleconf.get('options', {})
-
-            self.rules[rule_name] = Rule(
-                rule_name, rule['template'], options)
-
-        return self.rules[rule_name]
-
-    def rules_for_profile(self, profile_id):
-        try:
-            with self._open('profiles', '%s.json' % profile_id) as f:
-                profile = json.load(f)
-        except IOError:
-            raise errors.NotFound(
-                reason=_('No CSR generation rules are defined for profile'
-                         ' %(profile_id)s') % {'profile_id': profile_id})
-
-        field_mappings = []
-        for field in profile:
-            syntax_rule = self._rule(field['syntax'])
-            data_rules = [self._rule(name) for name in field['data']]
-            field_mappings.append(FieldMapping(
-                syntax_rule.name, syntax_rule, data_rules))
-        return field_mappings
-
-
-class CSRGenerator:
-    def __init__(self, rule_provider, formatter_class=OpenSSLFormatter):
-        self.rule_provider = rule_provider
-        self.formatter = formatter_class()
-
-    def csr_config(self, principal, config, profile_id):
-        render_data = {'subject': principal, 'config': config}
-
-        rules = self.rule_provider.rules_for_profile(profile_id)
-        template = self.formatter.build_template(rules)
-
-        try:
-            config = template.render(render_data)
-        except jinja2.UndefinedError:
-            logger.debug(traceback.format_exc())
-            raise errors.CSRTemplateError(reason=_(
-                'Template error when formatting certificate data'))
-
-        return config
-
-
-class CSRLibraryAdaptor:
-    def get_subject_public_key_info(self):
-        raise NotImplementedError('Use a subclass of CSRLibraryAdaptor')
-
-    def sign_csr(self, certification_request_info):
-        """Sign a CertificationRequestInfo.
-
-        :returns: bytes, a DER-encoded signed CSR.
-        """
-        raise NotImplementedError('Use a subclass of CSRLibraryAdaptor')
-
-
-class OpenSSLAdaptor:
-    def __init__(self, key=None, key_filename=None, password_filename=None):
-        """
-        Must provide either ``key_filename`` or ``key``.
-
-        """
-        if key_filename is not None:
-            with open(key_filename, 'rb') as key_file:
-                key_bytes = key_file.read()
-
-            password = None
-            if password_filename is not None:
-                with open(password_filename, 'rb') as password_file:
-                    password = password_file.read().strip()
-
-            self._key = load_pem_private_key(
-                key_bytes, password, default_backend())
-
-        elif key is not None:
-            self._key = key
-
-        else:
-            raise ValueError("Must provide 'key' or 'key_filename'")
-
-    def key(self):
-        return self._key
-
-    def get_subject_public_key_info(self):
-        pubkey_info = self.key().public_key().public_bytes(
-            Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
-        return pubkey_info
-
-    def sign_csr(self, certification_request_info):
-        reqinfo = decoder.decode(
-            certification_request_info, rfc2314.CertificationRequestInfo())[0]
-        csr = rfc2314.CertificationRequest()
-        csr.setComponentByName('certificationRequestInfo', reqinfo)
-
-        algorithm = rfc2314.SignatureAlgorithmIdentifier()
-        algorithm.setComponentByName(
-            'algorithm', univ.ObjectIdentifier(
-                '1.2.840.113549.1.1.11'))  # sha256WithRSAEncryption
-        csr.setComponentByName('signatureAlgorithm', algorithm)
-
-        signature = self.key().sign(
-            certification_request_info,
-            padding.PKCS1v15(),
-            hashes.SHA256()
-        )
-        asn1sig = univ.BitString("'{sig}'H".format(
-                                    sig=codecs.encode(signature, 'hex')
-                                    .decode('ascii'))
-                                 )
-        csr.setComponentByName('signature', asn1sig)
-        return encoder.encode(csr)
-
-
-class NSSAdaptor:
-    def __init__(self, database, password_filename):
-        self.database = database
-        self.password_filename = password_filename
-        self.nickname = base64.b32encode(os.urandom(40))
-
-    def get_subject_public_key_info(self):
-        temp_cn = base64.b32encode(os.urandom(40)).decode('ascii')
-
-        password_args = []
-        if self.password_filename is not None:
-            password_args = ['-f', self.password_filename]
-
-        subprocess.check_call(
-            ['certutil', '-S', '-n', self.nickname, '-s', 'CN=%s' % temp_cn,
-             '-x', '-t', ',,', '-d', self.database] + password_args)
-        cert_pem = subprocess.check_output(
-            ['certutil', '-L', '-n', self.nickname, '-a',
-             '-d', self.database] + password_args)
-
-        cert = load_pem_x509_certificate(cert_pem, default_backend())
-        pubkey_info = cert.public_key().public_bytes(
-            Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
-
-        return pubkey_info
-
-    def sign_csr(self, certification_request_info):
-        raise NotImplementedError('NSS is not yet supported')
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/plugins/cert.py freeipa-4.7.90.pre1/ipaclient/plugins/cert.py
--- freeipa-4.7.90.pre1.orig/ipaclient/plugins/cert.py	2019-04-29 17:06:41.645221012 +0200
+++ freeipa-4.7.90.pre1/ipaclient/plugins/cert.py	2019-05-06 18:31:28.384751096 +0200
@@ -21,8 +21,6 @@
 
 import base64
 
-import six
-
 from ipaclient.frontend import MethodOverride
 from ipalib import errors
 from ipalib import x509
@@ -31,9 +29,6 @@
 from ipalib.plugable import Registry
 from ipalib.text import _
 
-if six.PY3:
-    unicode = str
-
 register = Registry()
 
 
@@ -73,87 +68,12 @@
 
 @register(override=True, no_fail=True)
 class cert_request(CertRetrieveOverride):
-    takes_options = CertRetrieveOverride.takes_options + (
-        Str(
-            'database?',
-            label=_('Path to NSS database'),
-            doc=_('Path to NSS database to use for private key'),
-        ),
-        Str(
-            'private_key?',
-            label=_('Path to private key file'),
-            doc=_('Path to PEM file containing a private key'),
-        ),
-        Str(
-            'password_file?',
-            label=_(
-                'File containing a password for the private key or database'),
-        ),
-        Str(
-            'csr_profile_id?',
-            label=_('Name of CSR generation profile (if not the same as'
-                    ' profile_id)'),
-        ),
-    )
-
     def get_args(self):
         for arg in super(cert_request, self).get_args():
             if arg.name == 'csr':
                 arg = arg.clone_retype(arg.name, File, required=False)
             yield arg
 
-    def forward(self, csr=None, **options):
-        database = options.pop('database', None)
-        private_key = options.pop('private_key', None)
-        csr_profile_id = options.pop('csr_profile_id', None)
-        password_file = options.pop('password_file', None)
-
-        if csr is None:
-            # Deferred import, ipaclient.csrgen is expensive to load.
-            # see https://pagure.io/freeipa/issue/7484
-            from ipaclient import csrgen
-
-            if database:
-                adaptor = csrgen.NSSAdaptor(database, password_file)
-            elif private_key:
-                adaptor = csrgen.OpenSSLAdaptor(
-                    key_filename=private_key, password_filename=password_file)
-            else:
-                raise errors.InvocationError(
-                    message=u"One of 'database' or 'private_key' is required")
-
-            pubkey_info = adaptor.get_subject_public_key_info()
-            pubkey_info_b64 = base64.b64encode(pubkey_info)
-
-            # If csr_profile_id is passed, that takes precedence.
-            # Otherwise, use profile_id. If neither are passed, the default
-            # in cert_get_requestdata will be used.
-            profile_id = csr_profile_id
-            if profile_id is None:
-                profile_id = options.get('profile_id')
-
-            response = self.api.Command.cert_get_requestdata(
-                profile_id=profile_id,
-                principal=options.get('principal'),
-                public_key_info=pubkey_info_b64)
-
-            req_info_b64 = response['result']['request_info']
-            req_info = base64.b64decode(req_info_b64)
-
-            csr = adaptor.sign_csr(req_info)
-
-            if not csr:
-                raise errors.CertificateOperationError(
-                    error=(_('Generated CSR was empty')))
-
-        else:
-            if database is not None or private_key is not None:
-                raise errors.MutuallyExclusiveError(reason=_(
-                    "Options 'database' and 'private_key' are not compatible"
-                    " with 'csr'"))
-
-        return super(cert_request, self).forward(csr, **options)
-
 
 @register(override=True, no_fail=True)
 class cert_show(CertRetrieveOverride):
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/plugins/cert.py.orig freeipa-4.7.90.pre1/ipaclient/plugins/cert.py.orig
--- freeipa-4.7.90.pre1.orig/ipaclient/plugins/cert.py.orig	1970-01-01 01:00:00.000000000 +0100
+++ freeipa-4.7.90.pre1/ipaclient/plugins/cert.py.orig	2019-04-29 17:06:41.645221012 +0200
@@ -0,0 +1,215 @@
+# Authors:
+#   Andrew Wnuk <awnuk@redhat.com>
+#   Jason Gerard DeRose <jderose@redhat.com>
+#   John Dennis <jdennis@redhat.com>
+#
+# Copyright (C) 2009  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import base64
+
+import six
+
+from ipaclient.frontend import MethodOverride
+from ipalib import errors
+from ipalib import x509
+from ipalib import util
+from ipalib.parameters import BinaryFile, File, Flag, Str
+from ipalib.plugable import Registry
+from ipalib.text import _
+
+if six.PY3:
+    unicode = str
+
+register = Registry()
+
+
+class CertRetrieveOverride(MethodOverride):
+    takes_options = (
+        Str(
+            'certificate_out?',
+            doc=_('Write certificate (chain if --chain used) to file'),
+            include='cli',
+            cli_metavar='FILE',
+        ),
+    )
+
+    def forward(self, *args, **options):
+        if 'certificate_out' in options:
+            certificate_out = options.pop('certificate_out')
+            try:
+                util.check_writable_file(certificate_out)
+            except errors.FileError as e:
+                raise errors.ValidationError(name='certificate-out',
+                                             error=str(e))
+        else:
+            certificate_out = None
+
+        result = super(CertRetrieveOverride, self).forward(*args, **options)
+
+        if certificate_out is not None:
+            if options.get('chain', False):
+                certs = result['result']['certificate_chain']
+            else:
+                certs = [base64.b64decode(result['result']['certificate'])]
+            certs = (x509.load_der_x509_certificate(cert) for cert in certs)
+            x509.write_certificate_list(certs, certificate_out)
+
+        return result
+
+
+@register(override=True, no_fail=True)
+class cert_request(CertRetrieveOverride):
+    takes_options = CertRetrieveOverride.takes_options + (
+        Str(
+            'database?',
+            label=_('Path to NSS database'),
+            doc=_('Path to NSS database to use for private key'),
+        ),
+        Str(
+            'private_key?',
+            label=_('Path to private key file'),
+            doc=_('Path to PEM file containing a private key'),
+        ),
+        Str(
+            'password_file?',
+            label=_(
+                'File containing a password for the private key or database'),
+        ),
+        Str(
+            'csr_profile_id?',
+            label=_('Name of CSR generation profile (if not the same as'
+                    ' profile_id)'),
+        ),
+    )
+
+    def get_args(self):
+        for arg in super(cert_request, self).get_args():
+            if arg.name == 'csr':
+                arg = arg.clone_retype(arg.name, File, required=False)
+            yield arg
+
+    def forward(self, csr=None, **options):
+        database = options.pop('database', None)
+        private_key = options.pop('private_key', None)
+        csr_profile_id = options.pop('csr_profile_id', None)
+        password_file = options.pop('password_file', None)
+
+        if csr is None:
+            # Deferred import, ipaclient.csrgen is expensive to load.
+            # see https://pagure.io/freeipa/issue/7484
+            from ipaclient import csrgen
+
+            if database:
+                adaptor = csrgen.NSSAdaptor(database, password_file)
+            elif private_key:
+                adaptor = csrgen.OpenSSLAdaptor(
+                    key_filename=private_key, password_filename=password_file)
+            else:
+                raise errors.InvocationError(
+                    message=u"One of 'database' or 'private_key' is required")
+
+            pubkey_info = adaptor.get_subject_public_key_info()
+            pubkey_info_b64 = base64.b64encode(pubkey_info)
+
+            # If csr_profile_id is passed, that takes precedence.
+            # Otherwise, use profile_id. If neither are passed, the default
+            # in cert_get_requestdata will be used.
+            profile_id = csr_profile_id
+            if profile_id is None:
+                profile_id = options.get('profile_id')
+
+            response = self.api.Command.cert_get_requestdata(
+                profile_id=profile_id,
+                principal=options.get('principal'),
+                public_key_info=pubkey_info_b64)
+
+            req_info_b64 = response['result']['request_info']
+            req_info = base64.b64decode(req_info_b64)
+
+            csr = adaptor.sign_csr(req_info)
+
+            if not csr:
+                raise errors.CertificateOperationError(
+                    error=(_('Generated CSR was empty')))
+
+        else:
+            if database is not None or private_key is not None:
+                raise errors.MutuallyExclusiveError(reason=_(
+                    "Options 'database' and 'private_key' are not compatible"
+                    " with 'csr'"))
+
+        return super(cert_request, self).forward(csr, **options)
+
+
+@register(override=True, no_fail=True)
+class cert_show(CertRetrieveOverride):
+    def get_options(self):
+        for option in super(cert_show, self).get_options():
+            if option.name == 'out':
+                # skip server-defined --out
+                continue
+            if option.name == 'certificate_out':
+                # add --out as a deprecated alias of --certificate-out
+                option = option.clone_rename(
+                    'out',
+                    cli_name='certificate_out',
+                    deprecated_cli_aliases={'out'},
+                )
+            yield option
+
+    def forward(self, *args, **options):
+        try:
+            options['certificate_out'] = options.pop('out')
+        except KeyError:
+            pass
+
+        return super(cert_show, self).forward(*args, **options)
+
+
+@register(override=True, no_fail=True)
+class cert_remove_hold(MethodOverride):
+    has_output_params = (
+        Flag('unrevoked',
+            label=_('Unrevoked'),
+        ),
+        Str('error_string',
+            label=_('Error'),
+        ),
+    )
+
+
+@register(override=True, no_fail=True)
+class cert_find(MethodOverride):
+    takes_options = (
+        BinaryFile(
+            'file?',
+            label=_("Input filename"),
+            doc=_('File to load the certificate from.'),
+            include='cli',
+        ),
+    )
+
+    def forward(self, *args, **options):
+        if self.api.env.context == 'cli':
+            if 'certificate' in options and 'file' in options:
+                raise errors.MutuallyExclusiveError(
+                    reason=_("cannot specify both raw certificate and file"))
+            if 'certificate' not in options and 'file' in options:
+                options['certificate'] = x509.load_unknown_x509_certificate(
+                                            options.pop('file'))
+
+        return super(cert_find, self).forward(*args, **options)
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/plugins/csrgen.py freeipa-4.7.90.pre1/ipaclient/plugins/csrgen.py
--- freeipa-4.7.90.pre1.orig/ipaclient/plugins/csrgen.py	2019-04-29 17:06:41.669220677 +0200
+++ freeipa-4.7.90.pre1/ipaclient/plugins/csrgen.py	1970-01-01 01:00:00.000000000 +0100
@@ -1,128 +0,0 @@
-#
-# Copyright (C) 2016  FreeIPA Contributors see COPYING for license
-#
-
-import base64
-
-import six
-
-from ipalib import api
-from ipalib import errors
-from ipalib import output
-from ipalib import util
-from ipalib.frontend import Local, Str
-from ipalib.parameters import Bytes, Principal
-from ipalib.plugable import Registry
-from ipalib.text import _
-from ipapython import dogtag
-
-
-if six.PY3:
-    unicode = str
-
-register = Registry()
-
-__doc__ = _("""
-Commands to build certificate requests automatically
-""")
-
-
-@register()
-class cert_get_requestdata(Local):
-    __doc__ = _('Gather data for a certificate signing request.')
-
-    NO_CLI = True
-
-    takes_options = (
-        Principal(
-            'principal',
-            label=_('Principal'),
-            doc=_('Principal for this certificate (e.g.'
-                  ' HTTP/test.example.com)'),
-        ),
-        Str(
-            'profile_id?',
-            label=_('Profile ID'),
-            doc=_('CSR Generation Profile to use'),
-        ),
-        Bytes(
-            'public_key_info',
-            label=_('Subject Public Key Info'),
-            doc=_('DER-encoded SubjectPublicKeyInfo structure'),
-        ),
-        Str(
-            'out?',
-            doc=_('Write CertificationRequestInfo to file'),
-        ),
-    )
-
-    has_output = (
-        output.Output(
-            'result',
-            type=dict,
-            doc=_('Dictionary mapping variable name to value'),
-        ),
-    )
-
-    has_output_params = (
-        Str(
-            'request_info',
-            label=_('CertificationRequestInfo structure'),
-        )
-    )
-
-    def execute(self, *args, **options):
-        # Deferred import, ipaclient.csrgen is expensive to load.
-        # see https://pagure.io/freeipa/issue/7484
-        from ipaclient import csrgen
-        from ipaclient import csrgen_ffi
-
-        if 'out' in options:
-            util.check_writable_file(options['out'])
-
-        principal = options.get('principal')
-        profile_id = options.get('profile_id')
-        if profile_id is None:
-            profile_id = dogtag.DEFAULT_PROFILE
-        public_key_info = options.get('public_key_info')
-        public_key_info = base64.b64decode(public_key_info)
-
-        if self.api.env.in_server:
-            backend = self.api.Backend.ldap2
-        else:
-            backend = self.api.Backend.rpcclient
-        if not backend.isconnected():
-            backend.connect()
-
-        try:
-            if principal.is_host:
-                principal_obj = api.Command.host_show(
-                    principal.hostname, all=True)
-            elif principal.is_service:
-                principal_obj = api.Command.service_show(
-                    unicode(principal), all=True)
-            elif principal.is_user:
-                principal_obj = api.Command.user_show(
-                    principal.username, all=True)
-        except errors.NotFound:
-            raise errors.NotFound(
-                reason=_("The principal for this request doesn't exist."))
-        principal_obj = principal_obj['result']
-        config = api.Command.config_show()['result']
-
-        generator = csrgen.CSRGenerator(csrgen.FileRuleProvider())
-
-        csr_config = generator.csr_config(principal_obj, config, profile_id)
-        request_info = base64.b64encode(csrgen_ffi.build_requestinfo(
-            csr_config.encode('utf8'), public_key_info))
-
-        result = {}
-        if 'out' in options:
-            with open(options['out'], 'wb') as f:
-                f.write(request_info)
-        else:
-            result = dict(request_info=request_info)
-
-        return dict(
-            result=result
-        )
diff -urN freeipa-4.7.90.pre1.orig/ipaclient/setup.py freeipa-4.7.90.pre1/ipaclient/setup.py
--- freeipa-4.7.90.pre1.orig/ipaclient/setup.py	2019-04-29 17:06:41.393224529 +0200
+++ freeipa-4.7.90.pre1/ipaclient/setup.py	2019-05-06 18:33:16.002443738 +0200
@@ -41,13 +41,6 @@
             "ipaclient.remote_plugins.2_156",
             "ipaclient.remote_plugins.2_164",
         ],
-        package_data={
-            'ipaclient': [
-                'csrgen/profiles/*.json',
-                'csrgen/rules/*.json',
-                'csrgen/templates/*.tmpl',
-            ],
-        },
         install_requires=[
             "cryptography",
             "ipalib",
@@ -63,7 +56,6 @@
         extras_require={
             "install": ["ipaplatform"],
             "otptoken_yubikey": ["python-yubico", "pyusb"],
-            "csrgen": ["cffi", "jinja2"],
             "ldap": ["python-ldap"],  # ipapython.ipaldap
         },
         zip_safe=False,
diff -urN freeipa-4.7.90.pre1.orig/ipatests/test_ipaclient/data/test_csrgen/configs/caIPAserviceCert.conf freeipa-4.7.90.pre1/ipatests/test_ipaclient/data/test_csrgen/configs/caIPAserviceCert.conf
--- freeipa-4.7.90.pre1.orig/ipatests/test_ipaclient/data/test_csrgen/configs/caIPAserviceCert.conf	2019-04-29 17:06:49.265114643 +0200
+++ freeipa-4.7.90.pre1/ipatests/test_ipaclient/data/test_csrgen/configs/caIPAserviceCert.conf	1970-01-01 01:00:00.000000000 +0100
@@ -1,16 +0,0 @@
-[ req ]
-prompt = no
-encrypt_key = no
-
-distinguished_name = sec0
-req_extensions = sec2
-
-[ sec0 ]
-O=DOMAIN.EXAMPLE.COM
-CN=machine.example.com
-
-[ sec1 ]
-DNS = machine.example.com
-
-[ sec2 ]
-subjectAltName = @sec1
diff -urN freeipa-4.7.90.pre1.orig/ipatests/test_ipaclient/data/test_csrgen/configs/userCert.conf freeipa-4.7.90.pre1/ipatests/test_ipaclient/data/test_csrgen/configs/userCert.conf
--- freeipa-4.7.90.pre1.orig/ipatests/test_ipaclient/data/test_csrgen/configs/userCert.conf	2019-04-29 17:06:49.277114475 +0200
+++ freeipa-4.7.90.pre1/ipatests/test_ipaclient/data/test_csrgen/configs/userCert.conf	1970-01-01 01:00:00.000000000 +0100
@@ -1,16 +0,0 @@
-[ req ]
-prompt = no
-encrypt_key = no
-
-distinguished_name = sec0
-req_extensions = sec2
-
-[ sec0 ]
-O=DOMAIN.EXAMPLE.COM
-CN=testuser
-
-[ sec1 ]
-email = testuser@example.com
-
-[ sec2 ]
-subjectAltName = @sec1
diff -urN freeipa-4.7.90.pre1.orig/ipatests/test_ipaclient/data/test_csrgen/profiles/profile.json freeipa-4.7.90.pre1/ipatests/test_ipaclient/data/test_csrgen/profiles/profile.json
--- freeipa-4.7.90.pre1.orig/ipatests/test_ipaclient/data/test_csrgen/profiles/profile.json	2019-04-29 17:06:49.283114391 +0200
+++ freeipa-4.7.90.pre1/ipatests/test_ipaclient/data/test_csrgen/profiles/profile.json	1970-01-01 01:00:00.000000000 +0100
@@ -1,8 +0,0 @@
-[
-    {
-        "syntax": "basic",
-        "data": [
-            "options"
-        ]
-    }
-]
diff -urN freeipa-4.7.90.pre1.orig/ipatests/test_ipaclient/data/test_csrgen/rules/basic.json freeipa-4.7.90.pre1/ipatests/test_ipaclient/data/test_csrgen/rules/basic.json
--- freeipa-4.7.90.pre1.orig/ipatests/test_ipaclient/data/test_csrgen/rules/basic.json	2019-04-29 17:06:49.294114238 +0200
+++ freeipa-4.7.90.pre1/ipatests/test_ipaclient/data/test_csrgen/rules/basic.json	1970-01-01 01:00:00.000000000 +0100
@@ -1,5 +0,0 @@
-{
-  "rule": {
-    "template": "openssl_rule"
-  }
-}
diff -urN freeipa-4.7.90.pre1.orig/ipatests/test_ipaclient/data/test_csrgen/rules/options.json freeipa-4.7.90.pre1/ipatests/test_ipaclient/data/test_csrgen/rules/options.json
--- freeipa-4.7.90.pre1.orig/ipatests/test_ipaclient/data/test_csrgen/rules/options.json	2019-04-29 17:06:49.300114154 +0200
+++ freeipa-4.7.90.pre1/ipatests/test_ipaclient/data/test_csrgen/rules/options.json	1970-01-01 01:00:00.000000000 +0100
@@ -1,8 +0,0 @@
-{
-  "rule": {
-    "template": "openssl_rule"
-  },
-  "options": {
-    "rule_option": true
-  }
-}
diff -urN freeipa-4.7.90.pre1.orig/ipatests/test_ipaclient/data/test_csrgen/templates/identity_base.tmpl freeipa-4.7.90.pre1/ipatests/test_ipaclient/data/test_csrgen/templates/identity_base.tmpl
--- freeipa-4.7.90.pre1.orig/ipatests/test_ipaclient/data/test_csrgen/templates/identity_base.tmpl	2019-04-29 17:06:49.313113973 +0200
+++ freeipa-4.7.90.pre1/ipatests/test_ipaclient/data/test_csrgen/templates/identity_base.tmpl	1970-01-01 01:00:00.000000000 +0100
@@ -1 +0,0 @@
-{{ options|join(";") }}
diff -urN freeipa-4.7.90.pre1.orig/ipatests/test_ipaclient/test_csrgen.py freeipa-4.7.90.pre1/ipatests/test_ipaclient/test_csrgen.py
--- freeipa-4.7.90.pre1.orig/ipatests/test_ipaclient/test_csrgen.py	2019-04-29 17:06:49.251114838 +0200
+++ freeipa-4.7.90.pre1/ipatests/test_ipaclient/test_csrgen.py	1970-01-01 01:00:00.000000000 +0100
@@ -1,304 +0,0 @@
-#
-# Copyright (C) 2016  FreeIPA Contributors see COPYING for license
-#
-
-import os
-import pytest
-
-from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives.asymmetric import rsa
-from cryptography import x509
-
-from ipaclient import csrgen, csrgen_ffi
-from ipalib import errors
-
-BASE_DIR = os.path.dirname(__file__)
-CSR_DATA_DIR = os.path.join(BASE_DIR, 'data', 'test_csrgen')
-
-
-@pytest.fixture
-def formatter():
-    return csrgen.Formatter(csr_data_dir=CSR_DATA_DIR)
-
-
-@pytest.fixture
-def rule_provider():
-    return csrgen.FileRuleProvider(csr_data_dir=CSR_DATA_DIR)
-
-
-@pytest.fixture
-def generator():
-    return csrgen.CSRGenerator(csrgen.FileRuleProvider())
-
-
-class StubRuleProvider(csrgen.RuleProvider):
-    def __init__(self):
-        self.syntax_rule = csrgen.Rule(
-            'syntax', '{{datarules|join(",")}}', {})
-        self.data_rule = csrgen.Rule('data', 'data_template', {})
-        self.field_mapping = csrgen.FieldMapping(
-            'example', self.syntax_rule, [self.data_rule])
-        self.rules = [self.field_mapping]
-
-    def rules_for_profile(self, profile_id):
-        return self.rules
-
-
-class IdentityFormatter(csrgen.Formatter):
-    base_template_name = 'identity_base.tmpl'
-
-    def __init__(self):
-        super(IdentityFormatter, self).__init__(csr_data_dir=CSR_DATA_DIR)
-
-    def _get_template_params(self, syntax_rules):
-        return {'options': syntax_rules}
-
-
-class test_Formatter:
-    def test_prepare_data_rule_with_data_source(self, formatter):
-        data_rule = csrgen.Rule('uid', '{{subject.uid.0}}',
-                                {'data_source': 'subject.uid.0'})
-        prepared = formatter._prepare_data_rule(data_rule)
-        assert prepared == '{% if subject.uid.0 %}{{subject.uid.0}}{% endif %}'
-
-    def test_prepare_data_rule_no_data_source(self, formatter):
-        """Not a normal case, but we should handle it anyway"""
-        data_rule = csrgen.Rule('uid', 'static_text', {})
-        prepared = formatter._prepare_data_rule(data_rule)
-        assert prepared == 'static_text'
-
-    def test_prepare_syntax_rule_with_data_sources(self, formatter):
-        syntax_rule = csrgen.Rule(
-            'example', '{{datarules|join(",")}}', {})
-        data_rules = ['{{subject.field1}}', '{{subject.field2}}']
-        data_sources = ['subject.field1', 'subject.field2']
-        prepared = formatter._prepare_syntax_rule(
-            syntax_rule, data_rules, 'example', data_sources)
-
-        assert prepared == (
-            '{% if subject.field1 or subject.field2 %}{{subject.field1}},'
-            '{{subject.field2}}{% endif %}')
-
-    def test_prepare_syntax_rule_with_combinator(self, formatter):
-        syntax_rule = csrgen.Rule('example', '{{datarules|join(",")}}',
-                                  {'data_source_combinator': 'and'})
-        data_rules = ['{{subject.field1}}', '{{subject.field2}}']
-        data_sources = ['subject.field1', 'subject.field2']
-        prepared = formatter._prepare_syntax_rule(
-            syntax_rule, data_rules, 'example', data_sources)
-
-        assert prepared == (
-            '{% if subject.field1 and subject.field2 %}{{subject.field1}},'
-            '{{subject.field2}}{% endif %}')
-
-    def test_prepare_syntax_rule_required(self, formatter):
-        syntax_rule = csrgen.Rule('example', '{{datarules|join(",")}}',
-                                  {'required': True})
-        data_rules = ['{{subject.field1}}']
-        data_sources = ['subject.field1']
-        prepared = formatter._prepare_syntax_rule(
-            syntax_rule, data_rules, 'example', data_sources)
-
-        assert prepared == (
-            '{% filter required("example") %}{% if subject.field1 %}'
-            '{{subject.field1}}{% endif %}{% endfilter %}')
-
-    def test_prepare_syntax_rule_passthrough(self, formatter):
-        """
-        Calls to macros defined as passthrough are still call tags in the final
-        template.
-        """
-        formatter._define_passthrough('example.macro')
-
-        syntax_rule = csrgen.Rule(
-            'example',
-            '{% call example.macro() %}{{datarules|join(",")}}{% endcall %}',
-            {})
-        data_rules = ['{{subject.field1}}']
-        data_sources = ['subject.field1']
-        prepared = formatter._prepare_syntax_rule(
-            syntax_rule, data_rules, 'example', data_sources)
-
-        assert prepared == (
-            '{% if subject.field1 %}{% call example.macro() %}'
-            '{{subject.field1}}{% endcall %}{% endif %}')
-
-    def test_prepare_syntax_rule_no_data_sources(self, formatter):
-        """Not a normal case, but we should handle it anyway"""
-        syntax_rule = csrgen.Rule(
-            'example', '{{datarules|join(",")}}', {})
-        data_rules = ['rule1', 'rule2']
-        data_sources = []
-        prepared = formatter._prepare_syntax_rule(
-            syntax_rule, data_rules, 'example', data_sources)
-
-        assert prepared == 'rule1,rule2'
-
-
-class test_FileRuleProvider:
-    def test_rule_basic(self, rule_provider):
-        rule_name = 'basic'
-
-        rule = rule_provider._rule(rule_name)
-
-        assert rule.template == 'openssl_rule'
-
-    def test_rule_global_options(self, rule_provider):
-        rule_name = 'options'
-
-        rule = rule_provider._rule(rule_name)
-
-        assert rule.options['rule_option'] is True
-
-    def test_rule_nosuchrule(self, rule_provider):
-        with pytest.raises(errors.NotFound):
-            rule_provider._rule('nosuchrule')
-
-    def test_rules_for_profile_success(self, rule_provider):
-        rules = rule_provider.rules_for_profile('profile')
-
-        assert len(rules) == 1
-        field_mapping = rules[0]
-        assert field_mapping.syntax_rule.name == 'basic'
-        assert len(field_mapping.data_rules) == 1
-        assert field_mapping.data_rules[0].name == 'options'
-
-    def test_rules_for_profile_nosuchprofile(self, rule_provider):
-        with pytest.raises(errors.NotFound):
-            rule_provider.rules_for_profile('nosuchprofile')
-
-
-class test_CSRGenerator:
-    def test_userCert_OpenSSL(self, generator):
-        principal = {
-            'uid': ['testuser'],
-            'mail': ['testuser@example.com'],
-        }
-        config = {
-            'ipacertificatesubjectbase': [
-                'O=DOMAIN.EXAMPLE.COM'
-            ],
-        }
-
-        script = generator.csr_config(principal, config, 'userCert')
-        with open(os.path.join(
-                CSR_DATA_DIR, 'configs', 'userCert.conf')) as f:
-            expected_script = f.read()
-        assert script == expected_script
-
-    def test_caIPAserviceCert_OpenSSL(self, generator):
-        principal = {
-            'krbprincipalname': [
-                'HTTP/machine.example.com@DOMAIN.EXAMPLE.COM'
-            ],
-        }
-        config = {
-            'ipacertificatesubjectbase': [
-                'O=DOMAIN.EXAMPLE.COM'
-            ],
-        }
-
-        script = generator.csr_config(
-            principal, config, 'caIPAserviceCert')
-        with open(os.path.join(
-                CSR_DATA_DIR, 'configs', 'caIPAserviceCert.conf')) as f:
-            expected_script = f.read()
-        assert script == expected_script
-
-    def test_works_with_lowercase_attr_type_shortname(self, generator):
-        principal = {
-            'uid': ['testuser'],
-            'mail': ['testuser@example.com'],
-        }
-        template_env = {
-            'ipacertificatesubjectbase': [
-                'o=DOMAIN.EXAMPLE.COM'  # lower-case attr type shortname
-            ],
-        }
-        config = generator.csr_config(principal, template_env, 'userCert')
-
-        key = rsa.generate_private_key(
-            public_exponent=65537,
-            key_size=2048,
-            backend=default_backend(),
-        )
-        adaptor = csrgen.OpenSSLAdaptor(key=key)
-
-        reqinfo = bytes(csrgen_ffi.build_requestinfo(
-            config.encode('utf-8'), adaptor.get_subject_public_key_info()))
-        csr_der = adaptor.sign_csr(reqinfo)
-        csr = x509.load_der_x509_csr(csr_der, default_backend())
-        assert (
-            csr.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
-            == [x509.NameAttribute(x509.NameOID.COMMON_NAME, u'testuser')]
-        )
-        assert (
-            csr.subject.get_attributes_for_oid(x509.NameOID.ORGANIZATION_NAME)
-            == [x509.NameAttribute(
-                x509.NameOID.ORGANIZATION_NAME, u'DOMAIN.EXAMPLE.COM')]
-        )
-
-    def test_unrecognised_attr_type_raises(self, generator):
-        principal = {
-            'uid': ['testuser'],
-            'mail': ['testuser@example.com'],
-        }
-        template_env = {
-            'ipacertificatesubjectbase': [
-                'X=DOMAIN.EXAMPLE.COM'  # unrecognised attr type
-            ],
-        }
-        config = generator.csr_config(principal, template_env, 'userCert')
-
-        key = rsa.generate_private_key(
-            public_exponent=65537,
-            key_size=2048,
-            backend=default_backend(),
-        )
-        adaptor = csrgen.OpenSSLAdaptor(key=key)
-
-        with pytest.raises(
-                errors.CSRTemplateError,
-                message='unrecognised attribute type: X'):
-            csrgen_ffi.build_requestinfo(
-                config.encode('utf-8'), adaptor.get_subject_public_key_info())
-
-
-class test_rule_handling:
-    def test_optionalAttributeMissing(self, generator):
-        principal = {'uid': 'testuser'}
-        rule_provider = StubRuleProvider()
-        rule_provider.data_rule.template = '{{subject.mail}}'
-        rule_provider.data_rule.options = {'data_source': 'subject.mail'}
-        generator = csrgen.CSRGenerator(
-            rule_provider, formatter_class=IdentityFormatter)
-
-        script = generator.csr_config(
-            principal, {}, 'example')
-        assert script == '\n'
-
-    def test_twoDataRulesOneMissing(self, generator):
-        principal = {'uid': 'testuser'}
-        rule_provider = StubRuleProvider()
-        rule_provider.data_rule.template = '{{subject.mail}}'
-        rule_provider.data_rule.options = {'data_source': 'subject.mail'}
-        rule_provider.field_mapping.data_rules.append(csrgen.Rule(
-            'data2', '{{subject.uid}}', {'data_source': 'subject.uid'}))
-        generator = csrgen.CSRGenerator(
-            rule_provider, formatter_class=IdentityFormatter)
-
-        script = generator.csr_config(principal, {}, 'example')
-        assert script == ',testuser\n'
-
-    def test_requiredAttributeMissing(self):
-        principal = {'uid': 'testuser'}
-        rule_provider = StubRuleProvider()
-        rule_provider.data_rule.template = '{{subject.mail}}'
-        rule_provider.data_rule.options = {'data_source': 'subject.mail'}
-        rule_provider.syntax_rule.options = {'required': True}
-        generator = csrgen.CSRGenerator(
-            rule_provider, formatter_class=IdentityFormatter)
-
-        with pytest.raises(errors.CSRTemplateError):
-            _script = generator.csr_config(
-                principal, {}, 'example')