/*
 * 'OpenSSL for Ruby' project
 * Copyright (C) 2001 Michal Rokos <m.rokos@sh.cvut.cz>
 * All rights reserved.
 */
/*
 * This program is licensed under the same licence as Ruby.
 * (See the file 'COPYING'.)
 */
#if !defined(OSSL_PKEY_H)
#define OSSL_PKEY_H

extern VALUE mPKey;
extern VALUE cPKey;
extern VALUE ePKeyError;
extern const rb_data_type_t ossl_evp_pkey_type;

/* For ENGINE */
#define OSSL_PKEY_SET_PRIVATE(obj) rb_ivar_set((obj), rb_intern("private"), Qtrue)
#define OSSL_PKEY_IS_PRIVATE(obj)  (rb_attr_get((obj), rb_intern("private")) == Qtrue)

#define GetPKey(obj, pkey) do {\
    TypedData_Get_Struct((obj), EVP_PKEY, &ossl_evp_pkey_type, (pkey)); \
    if (!(pkey)) { \
        rb_raise(rb_eRuntimeError, "PKEY wasn't initialized!");\
    } \
} while (0)

/* Takes ownership of the EVP_PKEY */
VALUE ossl_pkey_wrap(EVP_PKEY *);
void ossl_pkey_check_public_key(const EVP_PKEY *);
EVP_PKEY *ossl_pkey_read_generic(BIO *, VALUE);
EVP_PKEY *GetPKeyPtr(VALUE);
EVP_PKEY *DupPKeyPtr(VALUE);
EVP_PKEY *GetPrivPKeyPtr(VALUE);

/*
 * Serializes _self_ in X.509 SubjectPublicKeyInfo format and returns the
 * resulting String. Sub-classes use this when overriding #to_der.
 */
VALUE ossl_pkey_export_spki(VALUE self, int to_der);
/*
 * Serializes the private key _self_ in the traditional private key format
 * and returns the resulting String. Sub-classes use this when overriding
 * #to_der.
 */
VALUE ossl_pkey_export_traditional(int argc, VALUE *argv, VALUE self,
                                   int to_der);

void Init_ossl_pkey(void);

/*
 * RSA
 */
extern VALUE cRSA;
void Init_ossl_rsa(void);

/*
 * DSA
 */
extern VALUE cDSA;
void Init_ossl_dsa(void);

/*
 * DH
 */
extern VALUE cDH;
void Init_ossl_dh(void);

/*
 * EC
 */
extern VALUE cEC;
VALUE ossl_ec_new(EVP_PKEY *);
void Init_ossl_ec(void);

#define OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, _name, _get)          \
/*                                                                      \
 *  call-seq:                                                           \
 *     _keytype##.##_name -> aBN                                        \
 */                                                                     \
static VALUE ossl_##_keytype##_get_##_name(VALUE self)                  \
{                                                                       \
        const _type *obj;                                               \
        const BIGNUM *bn;                                               \
                                                                        \
        Get##_type(self, obj);                                          \
        _get;                                                           \
        if (bn == NULL)                                                 \
                return Qnil;                                            \
        return ossl_bn_new(bn);                                         \
}

#define OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3)   \
        OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a1,                   \
                _type##_get0_##_group(obj, &bn, NULL, NULL))            \
        OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a2,                   \
                _type##_get0_##_group(obj, NULL, &bn, NULL))            \
        OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a3,                   \
                _type##_get0_##_group(obj, NULL, NULL, &bn))

#define OSSL_PKEY_BN_DEF_GETTER2(_keytype, _type, _group, a1, a2)       \
        OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a1,                   \
                _type##_get0_##_group(obj, &bn, NULL))                  \
        OSSL_PKEY_BN_DEF_GETTER0(_keytype, _type, a2,                   \
                _type##_get0_##_group(obj, NULL, &bn))

#ifndef OSSL_HAVE_IMMUTABLE_PKEY
#define OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3)   \
/*                                                                      \
 *  call-seq:                                                           \
 *     _keytype##.set_##_group(a1, a2, a3) -> self                      \
 */                                                                     \
static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2, VALUE v3) \
{                                                                       \
        _type *obj;                                                     \
        BIGNUM *bn1 = NULL, *orig_bn1 = NIL_P(v1) ? NULL : GetBNPtr(v1);\
        BIGNUM *bn2 = NULL, *orig_bn2 = NIL_P(v2) ? NULL : GetBNPtr(v2);\
        BIGNUM *bn3 = NULL, *orig_bn3 = NIL_P(v3) ? NULL : GetBNPtr(v3);\
                                                                        \
        Get##_type(self, obj);                                          \
        if ((orig_bn1 && !(bn1 = BN_dup(orig_bn1))) ||                  \
            (orig_bn2 && !(bn2 = BN_dup(orig_bn2))) ||                  \
            (orig_bn3 && !(bn3 = BN_dup(orig_bn3)))) {                  \
                BN_clear_free(bn1);                                     \
                BN_clear_free(bn2);                                     \
                BN_clear_free(bn3);                                     \
                ossl_raise(ePKeyError, "BN_dup");                       \
        }                                                               \
                                                                        \
        if (!_type##_set0_##_group(obj, bn1, bn2, bn3)) {               \
                BN_clear_free(bn1);                                     \
                BN_clear_free(bn2);                                     \
                BN_clear_free(bn3);                                     \
                ossl_raise(ePKeyError, #_type"_set0_"#_group);          \
        }                                                               \
        return self;                                                    \
}

#define OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2)       \
/*                                                                      \
 *  call-seq:                                                           \
 *     _keytype##.set_##_group(a1, a2) -> self                          \
 */                                                                     \
static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2) \
{                                                                       \
        _type *obj;                                                     \
        BIGNUM *bn1 = NULL, *orig_bn1 = NIL_P(v1) ? NULL : GetBNPtr(v1);\
        BIGNUM *bn2 = NULL, *orig_bn2 = NIL_P(v2) ? NULL : GetBNPtr(v2);\
                                                                        \
        Get##_type(self, obj);                                          \
        if ((orig_bn1 && !(bn1 = BN_dup(orig_bn1))) ||                  \
            (orig_bn2 && !(bn2 = BN_dup(orig_bn2)))) {                  \
                BN_clear_free(bn1);                                     \
                BN_clear_free(bn2);                                     \
                ossl_raise(ePKeyError, "BN_dup");                       \
        }                                                               \
                                                                        \
        if (!_type##_set0_##_group(obj, bn1, bn2)) {                    \
                BN_clear_free(bn1);                                     \
                BN_clear_free(bn2);                                     \
                ossl_raise(ePKeyError, #_type"_set0_"#_group);          \
        }                                                               \
        return self;                                                    \
}
#else
#define OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3)   \
static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2, VALUE v3) \
{                                                                       \
        rb_raise(ePKeyError,                                            \
                 #_keytype"#set_"#_group"= is incompatible with OpenSSL 3.0"); \
}

#define OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2)       \
static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2) \
{                                                                       \
        rb_raise(ePKeyError,                                            \
                 #_keytype"#set_"#_group"= is incompatible with OpenSSL 3.0"); \
}
#endif

#define OSSL_PKEY_BN_DEF3(_keytype, _type, _group, a1, a2, a3)          \
        OSSL_PKEY_BN_DEF_GETTER3(_keytype, _type, _group, a1, a2, a3)   \
        OSSL_PKEY_BN_DEF_SETTER3(_keytype, _type, _group, a1, a2, a3)

#define OSSL_PKEY_BN_DEF2(_keytype, _type, _group, a1, a2)              \
        OSSL_PKEY_BN_DEF_GETTER2(_keytype, _type, _group, a1, a2)       \
        OSSL_PKEY_BN_DEF_SETTER2(_keytype, _type, _group, a1, a2)

#define DEF_OSSL_PKEY_BN(class, keytype, name)                          \
        rb_define_method((class), #name, ossl_##keytype##_get_##name, 0)

#endif /* OSSL_PKEY_H */
