freeipa/ipaclient/csrgen_ffi.py
Stanislav Levin 7b8a2af219 Fix build_requestinfo in LibreSSL environments
`build_requestinfo` was broken in @ac6568dcf.
In this case LibreSSL behavior is the same as OpenSSL < 1.1.x.
Thus, an additional check for SSL implementation was added.

Fixes: https://pagure.io/freeipa/issue/7937
Signed-off-by: Stanislav Levin <slev@altlinux.org>
Reviewed-By: Christian Heimes <cheimes@redhat.com>
2019-05-14 15:58:40 +02:00

388 lines
11 KiB
Python

from cffi import FFI
import ctypes.util
from ipalib import errors
_ffi = FFI()
_ffi.cdef('''
/* libcrypto/crypto.h */
unsigned long OpenSSL_version_num(void);
unsigned long SSLeay(void);
const char * OpenSSL_version(int t);
const char * SSLeay_version(int t);
#define OPENSSL_VERSION 0
''')
_libcrypto = _ffi.dlopen(ctypes.util.find_library('crypto'))
# SSLeay_version has been renamed with OpenSSL_version in OpenSSL 1.1.0
# LibreSSL has OpenSSL_version since 2.7.0
try:
OpenSSL_version = _libcrypto.OpenSSL_version
except AttributeError:
OpenSSL_version = _libcrypto.SSLeay_version
_version = OpenSSL_version(_libcrypto.OPENSSL_VERSION)
_version = _ffi.string(_version).decode('utf-8')
LIBRESSL = _version.startswith('LibreSSL')
if not _version.startswith("OpenSSL") and not LIBRESSL:
raise ImportError("Only LibreSSL and OpenSSL are supported")
# SSLeay has been renamed with OpenSSL_version_num in OpenSSL 1.1.0
# LibreSSL has OpenSSL_version_num since 2.7.0
try:
OpenSSL_version_num = _libcrypto.OpenSSL_version_num
except AttributeError:
OpenSSL_version_num = _libcrypto.SSLeay
# OpenSSL_version_num()/SSLeay() returns the value of OPENSSL_VERSION_NUMBER
#
# OPENSSL_VERSION_NUMBER is a numeric release version identifier:
# MNNFFPPS: major minor fix patch status
# For example,
# 0x000906000 == 0.9.6 dev
# 0x000906023 == 0.9.6b beta 3
# 0x00090605f == 0.9.6e release
_openssl_version = OpenSSL_version_num()
_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_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;
''')
# since OpenSSL 1.1.0 req_info field is no longer pointer to X509_REQ_INFO
if _openssl_version >= 0x10100000 and not LIBRESSL:
_ffi.cdef('''
typedef struct X509_req_st {
X509_REQ_INFO req_info;
} X509_REQ;
''')
else:
_ffi.cdef('''
typedef struct X509_req_st {
X509_REQ_INFO *req_info;
} X509_REQ;
''')
_ffi.cdef('''
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
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()
if _openssl_version < 0x10100000 or LIBRESSL:
der_len = i2d_X509_REQ_INFO(req.req_info, NULL)
else:
req_info = _ffi.new("X509_REQ_INFO *", req.req_info)
der_len = i2d_X509_REQ_INFO(req_info, NULL)
req.req_info = req_info[0]
if der_len < 0:
_raise_openssl_errors()
der_buf = _ffi.new("unsigned char[%d]" % der_len)
der_out = _ffi.new("unsigned char **", der_buf)
if _openssl_version < 0x10100000 or LIBRESSL:
der_len = i2d_X509_REQ_INFO(req.req_info, der_out)
else:
der_len = i2d_X509_REQ_INFO(req_info, der_out)
req.req_info = req_info[0]
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)