package Crypt::Perl::ECDSA::ECParameters;
=encoding utf-8
=head1 NAME
Crypt::Perl::ECDSA::ECParameters - Parse RFC 3279 explicit curves
=head1 DISCUSSION
This interface is undocumented for now.
=cut
use strict;
use warnings;
use Try::Tiny;
use Crypt::Perl::BigInt ();
use Crypt::Perl::ECDSA::EncodedPoint ();
use Crypt::Perl::ECDSA::Utils ();
use Crypt::Perl::X ();
#NOTE: This needs never to use Crypt::Perl::ECDSA::DB
#so that extract_openssl_curves.pl will work.
use constant {
OID_ecPublicKey => '1.2.840.10045.2.1',
OID_prime_field => '1.2.840.10045.1.1',
OID_characteristic_two_field => '1.2.840.10045.1.2',
};
use constant EXPORTABLE => qw( p a b n h gx gy );
#cf. RFC 3279
use constant ASN1_ECParameters => q<
Trinomial ::= INTEGER
Pentanomial ::= SEQUENCE {
k1 INTEGER,
k2 INTEGER,
k3 INTEGER
}
FG_Basis_Parameters ::= CHOICE {
gnBasis NULL,
tpBasis Trinomial,
ppBasis Pentanomial
}
Characteristic-two ::= SEQUENCE {
m INTEGER,
basis OBJECT IDENTIFIER,
parameters FG_Basis_Parameters
}
FG_Field_Parameters ::= CHOICE {
prime-field INTEGER, -- p
characteristic-two Characteristic-two
}
FieldID ::= SEQUENCE {
fieldType OBJECT IDENTIFIER,
parameters FG_Field_Parameters
}
FieldElement ::= OCTET STRING
Curve ::= SEQUENCE {
a FieldElement,
b FieldElement,
seed BIT STRING OPTIONAL
}
ECPoint ::= OCTET STRING
ECPVer ::= INTEGER
-- Look for this.
ECParameters ::= SEQUENCE {
version ECPVer, -- always 1
fieldID FieldID,
curve Curve,
base ECPoint, -- generator
order INTEGER, -- n
-- ECDH needs it; ECDSA doesn’t (RFC 3279, p14)
cofactor INTEGER OPTIONAL -- h
}
>;
#This must return the same information as
#Crypt::Perl::ECDSA::EC::DB::get_curve_data_by_oid().
#
#It also expects the same structure that Convert::ASN1 parses,
#including array references for BIT STRINGs.
#
sub normalize {
my ($parsed_or_der) = @_;
my $params;
if (ref $parsed_or_der) {
$params = $parsed_or_der;
}
else {
die Crypt::Perl::X::create('Generic', 'TODO');
}
my $field_type = $params->{'fieldID'}{'fieldType'};
if ($field_type ne OID_prime_field() ) {
if ($field_type eq OID_characteristic_two_field() ) {
die Crypt::Perl::X::create('ECDSA::CharacteristicTwoUnsupported');
}
die Crypt::Perl::X::create('Generic', "Unknown field type OID: “$field_type”");
}
#“seed” isn’t necessary here for calculations (… right??)
my %curve = (
p => $params->{'fieldID'}{'parameters'}{'prime-field'},
a => $params->{'curve'}{'a'},
b => $params->{'curve'}{'b'},
n => $params->{'order'},
h => $params->{'cofactor'},
seed => $params->{'curve'}{'seed'}[0],
);
my @ints_to_upgrade = qw( p n );
if ( defined $curve{'h'} ) {
push @ints_to_upgrade, 'h';
}
#Ensure that numbers like 0 and 1 are represented as BigInt, too.
ref || ($_ = Crypt::Perl::BigInt->new($_)) for @curve{@ints_to_upgrade};
$_ = Crypt::Perl::BigInt->from_bytes($_) for @curve{'a', 'b'};
#We might receive the base point as compressed, uncompressed, or hybrid.
#Support all of those formats.
my $base = Crypt::Perl::ECDSA::EncodedPoint->new($params->{'base'})->get_uncompressed(\%curve);
@curve{'gx', 'gy'} = Crypt::Perl::ECDSA::Utils::split_G_or_public( $base );
my @strings_to_upgrade = qw( gx gy );
if ( defined $curve{'seed'} ) {
push @strings_to_upgrade, 'seed';
}
$_ = Crypt::Perl::BigInt->from_bytes($_) for @curve{@strings_to_upgrade};
defined($curve{$_}) || delete($curve{$_}) for qw( h seed );
#----------------------------------------------------------------------
# my $db_params;
#
# try {
# $db_params = Crypt::Perl::ECDSA::EC::DB::get_curve_name_by_data(\%curve);
# }
# catch {
# if ( !try { $_->isa('Crypt::Perl::X::ECDSA::NoCurveForParameters') } ) {
# local $@ = $_;
# die;
# }
# };
#
# #We only get here if there’s no cofactor
# #or if the one given is correct.
# if (!$curve{'h'} && !$db_params) {
# die Crypt::Perl::X::create('Generic', 'This library currently requires a cofactor (“h”) for custom curves.');
# }
#----------------------------------------------------------------------
# if ( $params->{'curve'}{'seed'} ) {
# $curve{'seed'} = Crypt::Perl::BigInt->from_bytes($params->{'curve'}{'seed'});
#
# #Make sure that the given seed is either for an unknown curve
# #or is correct for the given curve.
#
#
# #_get_db_params_or_undef(\%curve);
#
# #If it’s a known curve, verify that the seed matches.
# #if ($db_params) {
# # my $seed_hex = unpack 'H*', $params->{'curve'}{'seed'};
# #
# # if ($seed_hex ne $db_params->{'seed'}) {
# # Crypt::Perl::X::create('Generic', "Curve parameters match “$curve_name”, but the seed ($seed_hex) does not match expected value ($db_params->{'seed'})");
# # }
# #}
# }
#
# if (grep { !defined $curve{$_} } 'h', 'seed') {
# Module::Load::load('Crypt::Perl::ECDSA::EC::DB');
#
# #TODO: Would it be worthwhile to support arbitrary curves that don’t
# #give a cofactor? I’d need to figure out how to determine the
# #cofactor from the other parameters.
# my $curve_name = Crypt::Perl::ECDSA::EC::DB::get_curve_name_by_data(\%curve);
# my $params_hr = Crypt::Perl::ECDSA::EC::DB::get_curve_data_by_name($curve_name);
#
# @curve{'h', 'seed'} = @{$params}{'h', 'seed'};
# }
return \%curve;
}
#sub _get_db_params_or_undef {
# my ($curve_hr) = @_;
#
# my ($curve_name, $params_hr);
# try {
# $curve_name = Crypt::Perl::ECDSA::EC::DB::get_curve_name_by_data(\%curve);
# $params_hr = Crypt::Perl::ECDSA::EC::DB::get_curve_data_by_name($curve_name);
# }
# catch {
# if ( !try { $_->isa('Crypt::Perl::X::ECDSA::NoCurveForParameters') } ) {
# local $@ = $_;
# die;
# }
# };
#
# return $params_hr;
#}
#----------------------------------------------------------------------
sub _asn1 {
my ($class) = @_;
return Crypt::Perl::ASN1->new()->prepare($class->ASN1_ECParameters())->find('ECParameters');
}
1;