Crypt-OpenSSL-RSA

 view release on metacpan or  search on metacpan

RSA.xs  view on Meta::CPAN

        EVP_PKEY_free(pkey);
        return 0;
    }
    ok = PEM_write_bio_PrivateKey(bio, pkey, enc, pass, passlen, NULL, NULL);
    EVP_PKEY_free(pkey);
    return ok;
}
#endif

/* Pre-3.x helper for loading encrypted PKCS#8 DER private keys.
   Placed BEFORE the EVP_PKEY->RSA compatibility macros so that
   EVP_PKEY, EVP_PKEY_free, and EVP_PKEY_get1_RSA resolve to their
   real OpenSSL symbols. */
#if OPENSSL_VERSION_NUMBER < 0x30000000L
static RSA* _load_pkcs8_der_key(BIO* bio, const char* passphrase)
{
    EVP_PKEY* pkey;
    RSA* rsa;

    pkey = d2i_PKCS8PrivateKey_bio(bio, NULL, NULL, (void*)passphrase);
    if (!pkey)
        return NULL;

    rsa = EVP_PKEY_get1_RSA(pkey);
    EVP_PKEY_free(pkey);
    return rsa;
}
#endif

#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#define UNSIGNED_CHAR unsigned char
#define SIZE_T_INT size_t
#define SIZE_T_UNSIGNED_INT size_t
#define EVP_PKEY EVP_PKEY
#define EVP_PKEY_free(p) EVP_PKEY_free(p)
#define EVP_PKEY_get_size(p) EVP_PKEY_get_size(p)
#define PEM_read_bio_PrivateKey PEM_read_bio_PrivateKey
#define PEM_read_bio_RSAPublicKey PEM_read_bio_PUBKEY
#define PEM_read_bio_RSA_PUBKEY PEM_read_bio_PUBKEY
#define PEM_write_bio_PUBKEY(o,p) PEM_write_bio_PUBKEY(o,p)
#define PEM_write_bio_PrivateKey_traditional(m, n, o, p, q, r, s) PEM_write_bio_PrivateKey_traditional(m, n, o, p, q, r, s)
#else
#define UNSIGNED_CHAR char
#define SIZE_T_INT int
#define SIZE_T_UNSIGNED_INT unsigned int
#define EVP_PKEY RSA
#define EVP_PKEY_free(p) RSA_free(p)
#define EVP_PKEY_get_size(p) RSA_size(p)
#define PEM_read_bio_PrivateKey PEM_read_bio_RSAPrivateKey
#define PEM_read_bio_RSAPublicKey PEM_read_bio_RSAPublicKey
#define PEM_read_bio_RSA_PUBKEY PEM_read_bio_RSA_PUBKEY
#define PEM_write_bio_PUBKEY(o,p) PEM_write_bio_RSA_PUBKEY(o,p)
#define PEM_write_bio_PrivateKey_traditional(m, n, o, p, q, r, s) PEM_write_bio_RSAPrivateKey(m, n , o, p, q, r, s)
#endif

typedef struct
{
    EVP_PKEY* rsa;
    int padding;
    int hashMode;
    int is_private_key;  /* cached once at construction; avoids per-call BIGNUM alloc on 3.x */
} rsaData;

/* Key names for the rsa hash structure */

#define KEY_KEY "_Key"
#define PADDING_KEY "_Padding"
#define HASH_KEY "_Hash_Mode"

#define PACKAGE_NAME "Crypt::OpenSSL::RSA"

#ifdef LIBRESSL_VERSION_NUMBER
#define OLD_CRUFTY_SSL_VERSION (OPENSSL_VERSION_NUMBER < 0x10100000L || LIBRESSL_VERSION_NUMBER < 0x03050000fL)
#else
#define OLD_CRUFTY_SSL_VERSION (OPENSSL_VERSION_NUMBER < 0x10100000L)
#endif

void croakSsl(char* p_file, int p_line)
{
    const char* errorReason;
    unsigned long last_err = 0;
    unsigned long err;
    /* Drain the error queue and use the last (most recent) error,
       which is typically the most descriptive.  This also prevents
       stale errors from a previous eval-caught failure from leaking
       into the next croak message. */
    while ((err = ERR_get_error()) != 0) {
        last_err = err;
    }
    errorReason = ERR_reason_error_string(last_err);
    croak("%s:%d: OpenSSL error: %s", p_file, p_line,
          errorReason ? errorReason : "(unknown error)");
}

#define CHECK_OPEN_SSL(p_result) if (!(p_result)) croakSsl(__FILE__, __LINE__);
#define CHECK_OPEN_SSL_BIO(p_result, bio) \
    if (!(p_result)) { BIO_free(bio); croakSsl(__FILE__, __LINE__); }

#define PACKAGE_CROAK(p_message) croak("%s", (p_message))
#define CHECK_NEW(p_var, p_size, p_type) \
  if (New(0, p_var, p_size, p_type) == NULL) \
    { PACKAGE_CROAK("unable to alloc buffer"); }

#define THROW(p_result) if (!(p_result)) { error = 1; goto err; }

char _is_private(rsaData* p_rsa)
{
    return p_rsa->is_private_key;
}

static int _detect_private_key(EVP_PKEY* p_rsa)
{
#if OLD_CRUFTY_SSL_VERSION
    return (p_rsa->d != NULL);
#else
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    BIGNUM* d = NULL;
    EVP_PKEY_get_bn_param(p_rsa, OSSL_PKEY_PARAM_RSA_D, &d);
    if (d) {
        BN_clear_free(d);
        return 1;
    }
    return 0;
#else
    const BIGNUM* d = NULL;
    RSA_get0_key(p_rsa, NULL, NULL, &d);
    return (d != NULL);
#endif
#endif
}

SV* make_rsa_obj(SV* p_proto, EVP_PKEY* p_rsa)
{
    rsaData* rsa;

    CHECK_NEW(rsa, 1, rsaData);
    rsa->rsa = p_rsa;
#ifdef SHA512_DIGEST_LENGTH
    rsa->hashMode = NID_sha256;
#else
    rsa->hashMode = NID_sha1;
#endif
    rsa->padding = RSA_PKCS1_OAEP_PADDING;
    rsa->is_private_key = _detect_private_key(p_rsa);
    return sv_bless(
        newRV_noinc(newSViv((IV) rsa)),
        (SvROK(p_proto) ? SvSTASH(SvRV(p_proto)) : gv_stashsv(p_proto, 1)));
}

int get_digest_length(int hash_method)
{
    switch(hash_method)
    {
        case NID_md5:
            return MD5_DIGEST_LENGTH;
            break;
        case NID_sha1:
            return SHA_DIGEST_LENGTH;
            break;
#ifdef SHA512_DIGEST_LENGTH
        case NID_sha224:
            return SHA224_DIGEST_LENGTH;
            break;
        case NID_sha256:
            return SHA256_DIGEST_LENGTH;
            break;
        case NID_sha384:
            return SHA384_DIGEST_LENGTH;
            break;
        case NID_sha512:
            return SHA512_DIGEST_LENGTH;
            break;
#endif
        case NID_ripemd160:
            return RIPEMD160_DIGEST_LENGTH;
            break;
#ifdef WHIRLPOOL_DIGEST_LENGTH
        case NID_whirlpool:
            return WHIRLPOOL_DIGEST_LENGTH;
            break;
#endif
        default:
            croak("Unknown digest hash mode %u", hash_method);
            break;
    }
}
#if OPENSSL_VERSION_NUMBER >= 0x30000000L

EVP_MD *get_md_bynid(int hash_method)
{
    switch(hash_method)
    {
        case NID_md5:
            return EVP_MD_fetch(NULL, "md5", NULL);
            break;
        case NID_sha1:
            return EVP_MD_fetch(NULL, "sha1", NULL);
            break;
#ifdef SHA512_DIGEST_LENGTH
        case NID_sha224:
            return EVP_MD_fetch(NULL, "sha224", NULL);
            break;
        case NID_sha256:
            return EVP_MD_fetch(NULL, "sha256", NULL);

RSA.xs  view on Meta::CPAN

           padding choice.  OAEP and PSS are not valid here. */
        if (p_rsa->padding == RSA_PKCS1_OAEP_PADDING) {
            croak("OAEP padding is not supported for private_encrypt/public_decrypt. "
                  "Call use_no_padding() or use_pkcs1_padding() first.");
        }
        if (p_rsa->padding == RSA_PKCS1_PSS_PADDING) {
            croak("PSS padding with private_encrypt/public_decrypt is not supported. "
                  "Use sign()/verify() for PSS signatures.");
        }
    }

    ctx = EVP_PKEY_CTX_new_from_pkey(NULL, (EVP_PKEY* )p_rsa->rsa, NULL);

    THROW(ctx);

    THROW(init_crypt(ctx) == 1);
    THROW(EVP_PKEY_CTX_set_rsa_padding(ctx, p_rsa->padding) > 0);
    THROW(p_crypt(ctx, NULL, &to_length, from, from_length) == 1);
    Newx(to, to_length, UNSIGNED_CHAR);
    THROW(to);
    THROW(p_crypt(ctx, to, &to_length, from, from_length) == 1);

    EVP_PKEY_CTX_free(ctx);

    goto crypt_done;
    err:
        if (ctx) EVP_PKEY_CTX_free(ctx);
        Safefree(to);
        CHECK_OPEN_SSL(0);
    crypt_done:
#else
    size = EVP_PKEY_get_size(p_rsa->rsa);
    CHECK_NEW(to, size, UNSIGNED_CHAR);
    to_length = p_crypt(
       from_length, from, (unsigned char*) to, p_rsa->rsa, p_rsa->padding);
#endif
    if (to_length < 0)
    {
        Safefree(to);
        CHECK_OPEN_SSL(0);
    }
    sv = newSVpv((char* ) to, to_length);
    Safefree(to);
    return sv;
}

MODULE = Crypt::OpenSSL::RSA		PACKAGE = Crypt::OpenSSL::RSA
PROTOTYPES: DISABLE

BOOT:
#if OPENSSL_VERSION_NUMBER < 0x10100000L
    # might introduce memory leak without calling EVP_cleanup() on exit
    # see https://wiki.openssl.org/index.php/Library_Initialization
    ERR_load_crypto_strings();
    OpenSSL_add_all_algorithms();
#else
    # NOOP
#endif

SV*
_new_private_key_pem(proto, key_string_SV, passphrase_SV=&PL_sv_undef)
    SV* proto;
    SV* key_string_SV;
    SV* passphrase_SV;
  CODE:
    RETVAL = make_rsa_obj(
        proto, _load_rsa_key(key_string_SV, PEM_read_bio_PrivateKey, passphrase_SV));
  OUTPUT:
    RETVAL

SV*
_new_public_key_pkcs1(proto, key_string_SV)
    SV* proto;
    SV* key_string_SV;
  CODE:
    RETVAL = make_rsa_obj(
        proto, _load_rsa_key(key_string_SV, PEM_read_bio_RSAPublicKey, &PL_sv_undef));
  OUTPUT:
    RETVAL

SV*
_new_public_key_x509(proto, key_string_SV)
    SV* proto;
    SV* key_string_SV;
  CODE:
    RETVAL = make_rsa_obj(
        proto, _load_rsa_key(key_string_SV, PEM_read_bio_RSA_PUBKEY, &PL_sv_undef));
  OUTPUT:
    RETVAL

SV*
_new_public_key_x509_der(proto, key_string_SV)
    SV* proto;
    SV* key_string_SV;
  PREINIT:
    STRLEN keyStringLength;
    char* keyString;
    EVP_PKEY* pkey;
    BIO* bio;
  CODE:
    keyString = SvPV(key_string_SV, keyStringLength);
    CHECK_OPEN_SSL(bio = BIO_new_mem_buf(keyString, keyStringLength));
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    pkey = d2i_PUBKEY_bio(bio, NULL);
#else
    pkey = d2i_RSA_PUBKEY_bio(bio, NULL);
#endif
    BIO_free(bio);
    CHECK_OPEN_SSL(pkey);
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    if (EVP_PKEY_get_base_id(pkey) != EVP_PKEY_RSA) {
        EVP_PKEY_free(pkey);
        croak("The key loaded is not an RSA key");
    }
#endif
    RETVAL = make_rsa_obj(proto, pkey);
  OUTPUT:
    RETVAL

SV*
_new_public_key_pkcs1_der(proto, key_string_SV)
    SV* proto;
    SV* key_string_SV;
  PREINIT:
    STRLEN keyStringLength;
    char* keyString;
    EVP_PKEY* pkey;
    BIO* bio;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    OSSL_DECODER_CTX* dctx;
#endif
  CODE:
    keyString = SvPV(key_string_SV, keyStringLength);
    CHECK_OPEN_SSL(bio = BIO_new_mem_buf(keyString, keyStringLength));
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    pkey = NULL;
    dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, "DER", "type-specific",
                                          "RSA", OSSL_KEYMGMT_SELECT_PUBLIC_KEY,
                                          NULL, NULL);
    if (!dctx) {
        BIO_free(bio);
        croakSsl(__FILE__, __LINE__);
    }
    if (!OSSL_DECODER_from_bio(dctx, bio)) {
        OSSL_DECODER_CTX_free(dctx);
        BIO_free(bio);
        croakSsl(__FILE__, __LINE__);
    }
    OSSL_DECODER_CTX_free(dctx);
#else
    pkey = d2i_RSAPublicKey_bio(bio, NULL);
#endif
    BIO_free(bio);
    CHECK_OPEN_SSL(pkey);
    RETVAL = make_rsa_obj(proto, pkey);
  OUTPUT:
    RETVAL

SV*
_new_private_key_der(proto, key_string_SV, passphrase_SV=&PL_sv_undef)
    SV* proto;
    SV* key_string_SV;
    SV* passphrase_SV;
  PREINIT:
    STRLEN keyStringLength;
    char* keyString;
    EVP_PKEY* pkey;
    BIO* bio;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    OSSL_DECODER_CTX* dctx;
#endif
  CODE:
    keyString = SvPV(key_string_SV, keyStringLength);
    CHECK_OPEN_SSL(bio = BIO_new_mem_buf(keyString, keyStringLength));
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    pkey = NULL;
    dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, "DER", NULL,
                                          "RSA", OSSL_KEYMGMT_SELECT_ALL,
                                          NULL, NULL);
    if (!dctx) {
        BIO_free(bio);
        croakSsl(__FILE__, __LINE__);
    }
    if (SvPOK(passphrase_SV)) {
        STRLEN passlen;
        unsigned char* pass = (unsigned char*)SvPV(passphrase_SV, passlen);
        if (!OSSL_DECODER_CTX_set_passphrase(dctx, pass, passlen)) {
            OSSL_DECODER_CTX_free(dctx);
            BIO_free(bio);
            croakSsl(__FILE__, __LINE__);
        }
    }
    if (!OSSL_DECODER_from_bio(dctx, bio)) {
        OSSL_DECODER_CTX_free(dctx);
        BIO_free(bio);
        croakSsl(__FILE__, __LINE__);
    }
    OSSL_DECODER_CTX_free(dctx);
#else
    if (SvPOK(passphrase_SV)) {
        char* passphrase = SvPV_nolen(passphrase_SV);
        pkey = _load_pkcs8_der_key(bio, passphrase);
    } else {
        pkey = d2i_RSAPrivateKey_bio(bio, NULL);
    }
#endif
    BIO_free(bio);
    CHECK_OPEN_SSL(pkey);
    RETVAL = make_rsa_obj(proto, pkey);
  OUTPUT:
    RETVAL

void
DESTROY(p_rsa)
    rsaData* p_rsa;
  CODE:
    EVP_PKEY_free(p_rsa->rsa);
    Safefree(p_rsa);

SV*
get_private_key_string(p_rsa, passphrase_SV=&PL_sv_undef, cipher_name_SV=&PL_sv_undef)
    rsaData* p_rsa;
    SV* passphrase_SV;
    SV* cipher_name_SV;
  PREINIT:
    BIO* stringBIO;
    char* passphrase = NULL;
    STRLEN passphraseLength = 0;
    char* cipher_name;
    const EVP_CIPHER* enc = NULL;
  CODE:
    if (!_is_private(p_rsa))
    {
        croak("Public keys cannot export private key strings");
    }
    if (SvPOK(cipher_name_SV) && !SvPOK(passphrase_SV)) {
        croak("Passphrase is required for cipher");
    }
    if (SvPOK(passphrase_SV)) {
        passphrase = SvPV(passphrase_SV, passphraseLength);
        if (SvPOK(cipher_name_SV)) {
            cipher_name = SvPV_nolen(cipher_name_SV);
        }
        else {
            cipher_name = "des3";
        }
        enc = EVP_get_cipherbyname(cipher_name);
        if (enc == NULL) {
            croak("Unsupported cipher: %s", cipher_name);
        }
    }

    CHECK_OPEN_SSL(stringBIO = BIO_new(BIO_s_mem()));
    CHECK_OPEN_SSL_BIO(PEM_write_bio_PrivateKey_traditional(
        stringBIO, p_rsa->rsa, enc, (unsigned char* ) passphrase, passphraseLength, NULL, NULL), stringBIO);
    RETVAL = extractBioString(stringBIO);

  OUTPUT:
    RETVAL

SV*
get_private_key_pkcs8_string(p_rsa, passphrase_SV=&PL_sv_undef, cipher_name_SV=&PL_sv_undef)
    rsaData* p_rsa;
    SV* passphrase_SV;
    SV* cipher_name_SV;
  PREINIT:
    BIO* stringBIO;
    char* passphrase = NULL;
    STRLEN passphraseLength = 0;
    char* cipher_name;
    const EVP_CIPHER* enc = NULL;
  CODE:
    if (SvPOK(cipher_name_SV) && !SvPOK(passphrase_SV)) {
        croak("Passphrase is required for cipher");
    }
    if (SvPOK(passphrase_SV)) {
        passphrase = SvPV(passphrase_SV, passphraseLength);
        if (SvPOK(cipher_name_SV)) {
            cipher_name = SvPV_nolen(cipher_name_SV);
        }
        else {
            cipher_name = "des3";
        }
        enc = EVP_get_cipherbyname(cipher_name);
        if (enc == NULL) {
            croak("Unsupported cipher: %s", cipher_name);
        }
    }

    CHECK_OPEN_SSL(stringBIO = BIO_new(BIO_s_mem()));
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    CHECK_OPEN_SSL_BIO(PEM_write_bio_PrivateKey(
        stringBIO, p_rsa->rsa, enc, (unsigned char*) passphrase, passphraseLength, NULL, NULL), stringBIO);
#else
    CHECK_OPEN_SSL_BIO(_write_pkcs8_pem(
        stringBIO, p_rsa->rsa, enc, (unsigned char*) passphrase, passphraseLength), stringBIO);
#endif
    RETVAL = extractBioString(stringBIO);

  OUTPUT:
    RETVAL

SV*
get_public_key_string(p_rsa)
    rsaData* p_rsa;
  PREINIT:
    BIO* stringBIO;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    OSSL_ENCODER_CTX *ctx = NULL;
    int error = 0;
#endif
  CODE:
    CHECK_OPEN_SSL(stringBIO = BIO_new(BIO_s_mem()));
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
    ctx = OSSL_ENCODER_CTX_new_for_pkey(p_rsa->rsa, OSSL_KEYMGMT_SELECT_PUBLIC_KEY,
            "PEM", "PKCS1", NULL);
    THROW(ctx != NULL && OSSL_ENCODER_CTX_get_num_encoders(ctx));

    THROW(OSSL_ENCODER_to_bio(ctx, stringBIO) == 1);

    OSSL_ENCODER_CTX_free(ctx);
    ctx = NULL;



( run in 0.730 second using v1.01-cache-2.11-cpan-e1769b4cff6 )