package Crypt::Perl::RSA::PrivateKey;

=encoding utf-8

=head1 NAME

Crypt::Perl::RSA::PrivateKey - object representation of an RSA private key

=head1 SYNOPSIS

    #You’ll probably instantiate this class using Parser.pm
    #or Generate.pm.

    #cf. JSON Web Algorithms (RFC 7518, page 5)
    #These return an octet string.
    $sig = $prkey->sign_RS256($message);
    $sig = $prkey->sign_RS384($message);
    $sig = $prkey->sign_RS512($message);

    #These return 1 or 0 to indicate verification or non-verification.
    $prkey->verify_RS256($message, $sig);
    $prkey->verify_RS384($message, $sig);
    $prkey->verify_RS512($message, $sig);

    #----------------------------------------------------------------------

    my $enc = $prkey->encrypt_raw($payload);
    my $orig = $prkey->decrypt_raw($enc);

    #----------------------------------------------------------------------

    my $der = $prkey->to_der();
    my $pem = $prkey->to_pem();

    #For use in creating PKCS #10 CSRs and X.509 certificates
    my $pub_der = $prkey->to_subject_public_der();

    my $pbkey = $prkey->get_public_key();

    #----------------------------------------------------------------------

    $prkey->version();              #scalar, integer

    $prkey->size();                 #modulus length, in bits
    $prkey->modulus_byte_length();

    #----------------------------------------------------------------------
    # The following all return instances of Crypt::Perl::BigInt,
    # a subclass of Math::BigInt.
    # The pairs (e.g., modulus() and N()) are aliases.
    #----------------------------------------------------------------------

    $prkey->modulus();
    $prkey->N();

    $prkey->publicExponent();
    $prkey->E();

    $prkey->privateExponent();
    $prkey->D();

    $prkey->prime1();
    $prkey->P();

    $prkey->prime2();
    $prkey->Q();

    $prkey->exponent1();
    $prkey->DP();

    $prkey->exponent2();
    $prkey->DQ();

    $prkey->coefficient();
    $prkey->QINV();

=cut

use strict;
use warnings;

use parent qw(
    Crypt::Perl::RSA::KeyBase
);

use Module::Load ();

use Crypt::Perl::RNG ();
use Crypt::Perl::X ();

use constant _PEM_HEADER => 'RSA PRIVATE KEY';
use constant _ASN1_MACRO => 'RSAPrivateKey';

BEGIN {
    __PACKAGE__->mk_ro_accessors(
        qw(
        version
        publicExponent
        privateExponent
        prime1
        prime2
        exponent1
        exponent2
        coefficient
        )
    );

    *E = \&publicExponent;
    *D = \&privateExponent;

    *P = \&prime1;
    *Q = \&prime2;

    *DP = \&exponent1;
    *DQ = \&exponent2;

    *QINV = \&coefficient;

    *to_subject_public_der = __PACKAGE__->can('_to_subject_public_der');
}

sub sign_RS256 {
    my ($self, $msg) = @_;

    return $self->_sign($msg, 'Digest::SHA', 'sha256', 'PKCS1_v1_5');
}

sub sign_RS384 {
    my ($self, $msg) = @_;

    return $self->_sign($msg, 'Digest::SHA', 'sha384', 'PKCS1_v1_5');
}

sub sign_RS512 {
    my ($self, $msg) = @_;

    return $self->_sign($msg, 'Digest::SHA', 'sha512', 'PKCS1_v1_5');
}

sub get_public_key {
    my ($self) = @_;

    Module::Load::load('Crypt::Perl::RSA::PublicKey');

    return Crypt::Perl::RSA::PublicKey->new( {
        modulus => $self->{'modulus'},
        publicExponent => $self->{'publicExponent'},
    } );
}

sub get_struct_for_private_jwk {
    my ($self) = @_;

    Module::Load::load('MIME::Base64');

    my $jwk = $self->get_struct_for_public_jwk();

    my %augment = qw(
        d   D
        p   P
        q   Q
        dp  DP
        dq  DQ
        qi  QINV
    );

    for my $k (keys %augment) {
        my $accessor = $augment{$k};
        $jwk->{$k} = MIME::Base64::encode_base64url( $self->$accessor()->as_bytes() );
    }

    return $jwk;
}

#----------------------------------------------------------------------
#This function, in tandem with encrypt_raw(), represents the fundamental
#mathematical truth on which RSA rests.
#

sub decrypt_raw {
    my ($self, $x) = @_;

    $x = Crypt::Perl::BigInt->from_bytes($x);

    #jsrsasign avoids this when it has P and Q, which we have.
    #presumably that’s because privateExponent (D) is quite large,
    #so using it as an exponent is expensive.
    #return $self->bmodpow($self->{'privateExponent'}, $self->{'modulus'})->as_bytes();

    my $p = $self->P();
    my $q = $self->Q();

    my $p1 = $p->copy()->bdec();
    my $q1 = $q->copy()->bdec();

    my $xp = $x->copy()->bmod($p)->bmodpow( $self->D()->copy()->bmod($p1), $p );
    my $xq = $x->copy()->bmod($q)->bmodpow( $self->D()->copy()->bmod($q1), $q );

    #$xp->binc($p) while $xp->blt($xq);

    #return ($xq + ((($xp - $xq) * $self->QINV()) % $p) * $q)->as_bytes();

    my $diff = $xp->bsub($xq)->babs()->bmod($p)->bsub($p)->babs();

    $diff->bmul($self->QINV())->bmod($p)->bmuladd($q, $xq)->as_bytes();
}

#----------------------------------------------------------------------

sub _sign {
    my ($self, $msg, $hash_module, $hasher, $scheme) = @_;

    Module::Load::load($hash_module);

    my $dgst = $hash_module->can($hasher)->($msg);

    my $sig;

    if ($scheme eq 'PKCS1_v1_5') {
        Module::Load::load('Crypt::Perl::RSA::PKCS1_v1_5');

        my $sig_length = $self->modulus_byte_length();

        #The encoded length equals the length, in bytes,
        #of the key’s modulus.
        my $eb = Crypt::Perl::RSA::PKCS1_v1_5::encode(
            $dgst,
            $hasher,
            $sig_length,
        );

        #printf "PERL: %v02x\n", $eb;
        #print "mod byte length: " . Crypt::Perl::RSA::modulus_byte_length($key_obj) . $/;

        my $x = Crypt::Perl::BigInt->from_hex( unpack 'H*', $eb );

        $sig = $self->_transform($x)->as_bytes();

        substr( $sig, 0, 0 ) = "\0" x ($sig_length - length $sig);
    }
    else {
        die Crypt::Perl::X::create('Generic', "Unknown signature scheme: “$scheme”");
    }

    return $sig;
}

#RSA’s signing operation.
#This function is based on _modPow() in forge’s js/rsa.js.
#
#Returns a BigInt.
sub _transform {
    my ($self, $x) = @_;

    my $key_bytes_length = $self->modulus_byte_length();

    #cryptographic blinding
    my $r;
    do {
        $r = Crypt::Perl::BigInt->from_hex(
            Crypt::Perl::RNG::bytes_hex( $key_bytes_length ),
        );
    } while ($r->bge($self->N())) || ($r->bgcd($self->N())->bne(1));

    $x->bmul( $r->copy()->bmodpow($self->E(), $self->N()) )->bmod($self->N());

    #calculate xp and xq
    my $xp = $x->copy()->bmod($self->P())->bmodpow($self->DP(), $self->P());
    my $xq = $x->copy()->bmod($self->Q())->bmodpow($self->DQ(), $self->Q());

    #xp must be larger than xq to avoid signed bit usage
    $xp->badd($self->P()) while $xp->blt($xq);

    my $y = $xp->bsub($xq)->bmul($self->QINV())->bmod($self->P());

    #$y *= $self->Q();
    #$y += $xq;
    $y->bmuladd( $self->Q(), $xq );

    #remove effect of random for cryptographic blinding
    $y->bmul( $r->bmodinv($self->N()) );
    $y->bmod($self->N());

    return $y;
}

1;
