#include "xs/openssl.h"

MODULE = Crypt::Keyczar		PACKAGE = Crypt::Keyczar::RsaPrivateKeyEngine


Crypt::Keyczar::RsaPrivateKeyEngine
new(class, modulus, publicExponent, privateExponent, primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient)
	SV *class
	SV *modulus
	SV *publicExponent
	SV *privateExponent
	SV *primeP
	SV *primeQ
	SV *primeExponentP
	SV *primeExponentQ
	SV *crtCoefficient
	CODE:
	{
	    RSA *rsa;
	    int rc;
	    STRLEN l;
	    unsigned char *in;
	    BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp;

	    PERL_UNUSED_VAR(class);
	    if ((rsa = RSA_new()) == NULL) {
	        crypt__keyczar__util__croak_openssl();
	        /* NOTREACHED */
	    }

	    in = (unsigned char *)SvPV(modulus, l);
	    n = BN_bin2bn((const unsigned char *)in, l, NULL);
            if (n == NULL) {
	        RSA_free(rsa);
	        crypt__keyczar__util__croak_openssl();
	        /* NOTREACHED */
	    }
	    in = (unsigned char *)SvPV(publicExponent, l);
	    e = BN_bin2bn((const unsigned char *)in, l, NULL);
            if (e == NULL) {
	        RSA_free(rsa);
	        crypt__keyczar__util__croak_openssl();
	        /* NOTREACHED */
	    }
	    in = (unsigned char *)SvPV(privateExponent, l);
	    d = BN_bin2bn((const unsigned char *)in, l, NULL);
            if (d == NULL) {
	        RSA_free(rsa);
	        crypt__keyczar__util__croak_openssl();
	        /* NOTREACHED */
	    }
	    in = (unsigned char *)SvPV(primeP, l);
	    p = BN_bin2bn((const unsigned char *)in, l, NULL);
            if (p == NULL) {
	        RSA_free(rsa);
	        crypt__keyczar__util__croak_openssl();
	        /* NOTREACHED */
	    }
	    in = (unsigned char *)SvPV(primeQ, l);
	    q = BN_bin2bn((const unsigned char *)in, l, NULL);
            if (q == NULL) {
	        RSA_free(rsa);
	        crypt__keyczar__util__croak_openssl();
	        /* NOTREACHED */
	    }
	    in = (unsigned char *)SvPV(primeExponentP, l);
	    dmp1 = BN_bin2bn((const unsigned char *)in, l, NULL);
            if (dmp1 == NULL) {
	        RSA_free(rsa);
	        crypt__keyczar__util__croak_openssl();
	        /* NOTREACHED */
	    }
	    in = (unsigned char *)SvPV(primeExponentQ, l);
	    dmq1 = BN_bin2bn((const unsigned char *)in, l, NULL);
            if (dmq1 == NULL) {
	        RSA_free(rsa);
	        crypt__keyczar__util__croak_openssl();
	        /* NOTREACHED */
	    }
	    in = (unsigned char *)SvPV(crtCoefficient, l);
	    iqmp = BN_bin2bn((const unsigned char *)in, l, NULL);
            if (iqmp == NULL) {
	        RSA_free(rsa);
	        crypt__keyczar__util__croak_openssl();
	        /* NOTREACHED */
	    }
	    if (RSA_set0_key(rsa, n, e, d) == 0) {
	        RSA_free(rsa);
	        crypt__keyczar__util__croak_openssl();
	        /* NOTREACHED */
	    }
	    if (RSA_set0_factors(rsa, p, q) == 0) {
	        RSA_free(rsa);
	        crypt__keyczar__util__croak_openssl();
	        /* NOTREACHED */
	    }
	    if (RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp) == 0) {
	        RSA_free(rsa);
	        crypt__keyczar__util__croak_openssl();
	        /* NOTREACHED */
	    }
	    rc = RSA_check_key(rsa);
	    if (rc == 0) {
	        RSA_free(rsa);
	        croak("invalid RSA Private key");
	    }
	    else if (rc == -1) {
	        RSA_free(rsa);
	        crypt__keyczar__util__croak_openssl();
	        /* NOTREACHED */
	    }
	    Newz(0, RETVAL, 1, struct Crypt__Keyczar__RsaPrivateKeyEngine_class);
	    RETVAL->rsa = rsa;
	    RETVAL->message = NULL;
	}
	OUTPUT:
	    RETVAL


void
DESTROY(self)
	Crypt::Keyczar::RsaPrivateKeyEngine self
	CODE:
	{
	    if (self->rsa != NULL) {
	        RSA_free(self->rsa);
	        self->rsa = NULL;
	    }
	    if (self->message != NULL) {
	        EVP_MD_CTX_destroy(self->message);
	        self->message = NULL;
	    }
	    Safefree(self);
	}


SV *
init(self, ...)
	Crypt::Keyczar::RsaPrivateKeyEngine self
	CODE:
	{
	    RETVAL = newSVpv("", 0);
	}
	OUTPUT:
	    RETVAL

SV *
decrypt(self, data)
	Crypt::Keyczar::RsaPrivateKeyEngine self
	SV *data
	CODE:
	{
	    unsigned char *in, *out;
	    int size = RSA_size(self->rsa);
	    int out_l;
	    STRLEN in_l;

	    Newz(0, out, size, unsigned char);
	    in = (unsigned char *)SvPV(data, in_l);
	    out_l = RSA_private_decrypt(in_l, in, out, self->rsa, RSA_PKCS1_OAEP_PADDING);
	    if (out_l == -1) {
	        Safefree(out);
	        crypt__keyczar__util__croak_openssl();
	        /* NOTREACHED */
	    }
	    RETVAL = newSVpv((char *)out, out_l);
	    Safefree(out);
	}
	OUTPUT:
	    RETVAL



HV *
generate(class, size)
	SV *class
	int size
	CODE:
	{
	    RSA *rsa;
	    BIGNUM *e, *n, *ee, *d, *p, *q, *dmp1, *dmq1, *iqmp;

	    PERL_UNUSED_VAR(class);

	    e = BN_new();
	    BN_set_word(e, RSA_F4);
	    rsa = RSA_new();
	    if (RSA_generate_key_ex(rsa, size, e, NULL) == 0) {
	        crypt__keyczar__util__croak_openssl();
	        /* NOTREACHED */
	    }

	    RSA_get0_key(rsa, (const BIGNUM **)&n, (const BIGNUM **)&ee, (const BIGNUM **)&d);
	    RETVAL = newHV();
	    if (crypt__keyczar__util__bignum2hv(n, "modulus", RETVAL) == 0) {
	        RSA_free(rsa);
	        croak("cannot generate RSA key: set 'modulus' fail");
	    }
	    if (crypt__keyczar__util__bignum2hv(ee, "publicExponent", RETVAL) == 0) {
	        RSA_free(rsa);
	        croak("cannot generate RSA key: set 'publicExponent' fail");
	    }
	    if (crypt__keyczar__util__bignum2hv(d, "privateExponent", RETVAL) == 0) {
	        RSA_free(rsa);
	        croak("cannot generate RSA key: set 'privateExponent' fail");
	    }

	    RSA_get0_factors(rsa, (const BIGNUM **)&p, (const BIGNUM **)&q);
	    if (crypt__keyczar__util__bignum2hv(p, "primeP", RETVAL) == 0) {
	        RSA_free(rsa);
	        croak("cannot generate RSA key: set 'primeP' fail");
	    }
	    if (crypt__keyczar__util__bignum2hv(q, "primeQ", RETVAL) == 0) {
	        RSA_free(rsa);
	        croak("cannot generate RSA key: set 'primeQ' fail");
	    }

	    RSA_get0_crt_params(rsa, (const BIGNUM **)&dmp1, (const BIGNUM **)&dmq1, (const BIGNUM **)&iqmp);
	    if (crypt__keyczar__util__bignum2hv(dmp1, "primeExponentP", RETVAL) == 0) {
	        RSA_free(rsa);
	        croak("cannot generate RSA key: set 'primeExponentP' fail");
	    }
	    if (crypt__keyczar__util__bignum2hv(dmq1, "primeExponentQ", RETVAL) == 0) {
	        RSA_free(rsa);
	        croak("cannot generate RSA key: set 'primeExponentQ' fail");
	    }
	    if (crypt__keyczar__util__bignum2hv(iqmp, "crtCoefficient", RETVAL) == 0) {
	        RSA_free(rsa);
	        croak("cannot generate RSA key: set 'crtCoefficient' fail");
	    }
	    RSA_free(rsa);
	    BN_free(e);
	}
	OUTPUT:
	    RETVAL


void
update(self, ...)
	Crypt::Keyczar::RsaPrivateKeyEngine self
	CODE:
	{
	    int i;
	    SV *data;
	    STRLEN in_l;
	    unsigned char *in;
	
	    if (self->message == NULL) {
	        self->message = EVP_MD_CTX_create();
	        if (!EVP_DigestInit_ex(self->message, EVP_sha1(), NULL)) {
	            croak("cannot initialize SHA1 context");
	        }
	    }

	    for (i = 1; i < items; i++) {
	        data = ST(i);
	        in = (unsigned char *)SvPV(data, in_l);
	        if (!EVP_DigestUpdate(self->message, in, in_l)) {
	            croak("cannot update SHA1 context");
	        }
	    }
	}

SV *
sign(self)
	Crypt::Keyczar::RsaPrivateKeyEngine self
	CODE:
	{
	    int rc;
	    unsigned char md[EVP_MAX_MD_SIZE];
	    unsigned int md_l = 0;
	    unsigned char *sign;
	    int size = RSA_size(self->rsa);
	    unsigned int sign_l;
	
	    if (self->message == NULL) {
	        RETVAL = 0;
	        return;
	    }
	    EVP_DigestFinal_ex(self->message, md, &md_l);
	    Newz(0, sign, size, unsigned char);
	    rc = RSA_sign(NID_sha1, md, md_l, sign, &sign_l, self->rsa);
	    if (rc != 1) {
	        Safefree(sign);
	        crypt__keyczar__util__croak_openssl();
	        /* NOTREACHED */
	    }
	    RETVAL = newSVpv((char *)sign, sign_l);
	    Safefree(sign);
	}
	OUTPUT:
	    RETVAL


int
verify(self, mac)
	Crypt::Keyczar::RsaPrivateKeyEngine self
	SV *mac
	CODE:
	{
	    int rc;
	    unsigned char md[EVP_MAX_MD_SIZE];
	    unsigned int l = 0;
	    STRLEN in_l;
	    unsigned char *in;
	
	    if (self->message == NULL) {
	        RETVAL = 0;
	        return;
	    }

	    EVP_DigestFinal_ex(self->message, md, &l);
	    in = (unsigned char *)SvPV(mac, in_l);
	    rc = RSA_verify(NID_sha1, md, l, in, in_l, self->rsa);
            RETVAL = rc == 1;
	}
	OUTPUT:
	    RETVAL


int
digest_size(self)
	Crypt::Keyczar::RsaPrivateKeyEngine self
	CODE:
	{
	    RETVAL = RSA_size(self->rsa);
	}
	OUTPUT:
	    RETVAL