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 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 +# Jason Gerard DeRose +# John Dennis +# +# 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 . + +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')