File Coverage

lib/Crypt/Perl/ECDSA/ECParameters.pm
Criterion Covered Total %
statement 52 56 92.8
branch 7 10 70.0
condition 4 6 66.6
subroutine 11 12 91.6
pod 0 1 0.0
total 74 85 87.0


line stmt bran cond sub pod time code
1             package Crypt::Perl::ECDSA::ECParameters;
2              
3             =encoding utf-8
4              
5             =head1 NAME
6              
7             Crypt::Perl::ECDSA::ECParameters - Parse RFC 3279 explicit curves
8              
9             =head1 DISCUSSION
10              
11             This interface is undocumented for now.
12              
13             =cut
14              
15 7     7   37 use strict;
  7         15  
  7         173  
16 7     7   57 use warnings;
  7         20  
  7         171  
17              
18 7     7   35 use Try::Tiny;
  7         9  
  7         349  
19              
20 7     7   59 use Crypt::Perl::BigInt ();
  7         14  
  7         119  
21 7     7   2526 use Crypt::Perl::ECDSA::EncodedPoint ();
  7         17  
  7         120  
22 7     7   40 use Crypt::Perl::ECDSA::Utils ();
  7         12  
  7         77  
23 7     7   27 use Crypt::Perl::X ();
  7         12  
  7         142  
24              
25             #NOTE: This needs never to use Crypt::Perl::ECDSA::DB
26             #so that extract_openssl_curves.pl will work.
27              
28             use constant {
29 7         634 OID_ecPublicKey => '1.2.840.10045.2.1',
30             OID_prime_field => '1.2.840.10045.1.1',
31             OID_characteristic_two_field => '1.2.840.10045.1.2',
32 7     7   27 };
  7         10  
33              
34 7     7   39 use constant EXPORTABLE => qw( p a b n h gx gy );
  7         11  
  7         498  
35              
36             #cf. RFC 3279
37 7         2908 use constant ASN1_ECParameters => q<
38             Trinomial ::= INTEGER
39              
40             Pentanomial ::= SEQUENCE {
41             k1 INTEGER,
42             k2 INTEGER,
43             k3 INTEGER
44             }
45              
46             FG_Basis_Parameters ::= CHOICE {
47             gnBasis NULL,
48             tpBasis Trinomial,
49             ppBasis Pentanomial
50             }
51              
52             Characteristic-two ::= SEQUENCE {
53             m INTEGER,
54             basis OBJECT IDENTIFIER,
55             parameters FG_Basis_Parameters
56             }
57              
58             FG_Field_Parameters ::= CHOICE {
59             prime-field INTEGER, -- p
60             characteristic-two Characteristic-two
61             }
62              
63             FieldID ::= SEQUENCE {
64             fieldType OBJECT IDENTIFIER,
65             parameters FG_Field_Parameters
66             }
67              
68             FieldElement ::= OCTET STRING
69              
70             Curve ::= SEQUENCE {
71             a FieldElement,
72             b FieldElement,
73             seed BIT STRING OPTIONAL
74             }
75              
76             ECPoint ::= OCTET STRING
77              
78             ECPVer ::= INTEGER
79              
80             -- Look for this.
81             ECParameters ::= SEQUENCE {
82             version ECPVer, -- always 1
83             fieldID FieldID,
84             curve Curve,
85             base ECPoint, -- generator
86             order INTEGER, -- n
87              
88             -- ECDH needs it; ECDSA doesn’t (RFC 3279, p14)
89             cofactor INTEGER OPTIONAL -- h
90             }
91 7     7   45 >;
  7         15  
92              
93             #This must return the same information as
94             #Crypt::Perl::ECDSA::EC::DB::get_curve_data_by_oid().
95             #
96             #It also expects the same structure that Convert::ASN1 parses,
97             #including array references for BIT STRINGs.
98             #
99             sub normalize {
100 258     258 0 631 my ($parsed_or_der) = @_;
101              
102 258         416 my $params;
103 258 50       979 if (ref $parsed_or_der) {
104 258         739 $params = $parsed_or_der;
105             }
106             else {
107 0         0 die Crypt::Perl::X::create('Generic', 'TODO');
108             }
109              
110 258         787 my $field_type = $params->{'fieldID'}{'fieldType'};
111 258 100       900 if ($field_type ne OID_prime_field() ) {
112 120 50       1029 if ($field_type eq OID_characteristic_two_field() ) {
113 120         1085 die Crypt::Perl::X::create('ECDSA::CharacteristicTwoUnsupported');
114             }
115              
116 0         0 die Crypt::Perl::X::create('Generic', "Unknown field type OID: “$field_type”");
117             }
118              
119             #“seed” isn’t necessary here for calculations (… right??)
120             my %curve = (
121             p => $params->{'fieldID'}{'parameters'}{'prime-field'},
122             a => $params->{'curve'}{'a'},
123             b => $params->{'curve'}{'b'},
124             n => $params->{'order'},
125             h => $params->{'cofactor'},
126 138         1546 seed => $params->{'curve'}{'seed'}[0],
127             );
128              
129 138         627 my @ints_to_upgrade = qw( p n );
130 138 50       423 if ( defined $curve{'h'} ) {
131 138         294 push @ints_to_upgrade, 'h';
132             }
133              
134             #Ensure that numbers like 0 and 1 are represented as BigInt, too.
135 138   66     1037 ref || ($_ = Crypt::Perl::BigInt->new($_)) for @curve{@ints_to_upgrade};
136              
137 138         5948 $_ = Crypt::Perl::BigInt->from_bytes($_) for @curve{'a', 'b'};
138              
139             #We might receive the base point as compressed, uncompressed, or hybrid.
140             #Support all of those formats.
141 138         95678 my $base = Crypt::Perl::ECDSA::EncodedPoint->new($params->{'base'})->get_uncompressed(\%curve);
142 138         1505 @curve{'gx', 'gy'} = Crypt::Perl::ECDSA::Utils::split_G_or_public( $base );
143              
144 138         597 my @strings_to_upgrade = qw( gx gy );
145 138 100       791 if ( defined $curve{'seed'} ) {
146 67         211 push @strings_to_upgrade, 'seed';
147             }
148              
149 138         912 $_ = Crypt::Perl::BigInt->from_bytes($_) for @curve{@strings_to_upgrade};
150              
151 138   66     86516 defined($curve{$_}) || delete($curve{$_}) for qw( h seed );
152              
153             #----------------------------------------------------------------------
154             # my $db_params;
155             #
156             # try {
157             # $db_params = Crypt::Perl::ECDSA::EC::DB::get_curve_name_by_data(\%curve);
158             # }
159             # catch {
160             # if ( !try { $_->isa('Crypt::Perl::X::ECDSA::NoCurveForParameters') } ) {
161             # local $@ = $_;
162             # die;
163             # }
164             # };
165             #
166             # #We only get here if there’s no cofactor
167             # #or if the one given is correct.
168             # if (!$curve{'h'} && !$db_params) {
169             # die Crypt::Perl::X::create('Generic', 'This library currently requires a cofactor (“h”) for custom curves.');
170             # }
171             #----------------------------------------------------------------------
172              
173             # if ( $params->{'curve'}{'seed'} ) {
174             # $curve{'seed'} = Crypt::Perl::BigInt->from_bytes($params->{'curve'}{'seed'});
175             #
176             # #Make sure that the given seed is either for an unknown curve
177             # #or is correct for the given curve.
178             #
179             #
180             # #_get_db_params_or_undef(\%curve);
181             #
182             # #If it’s a known curve, verify that the seed matches.
183             # #if ($db_params) {
184             # # my $seed_hex = unpack 'H*', $params->{'curve'}{'seed'};
185             # #
186             # # if ($seed_hex ne $db_params->{'seed'}) {
187             # # Crypt::Perl::X::create('Generic', "Curve parameters match “$curve_name”, but the seed ($seed_hex) does not match expected value ($db_params->{'seed'})");
188             # # }
189             # #}
190             # }
191             #
192             # if (grep { !defined $curve{$_} } 'h', 'seed') {
193             # Module::Load::load('Crypt::Perl::ECDSA::EC::DB');
194             #
195             # #TODO: Would it be worthwhile to support arbitrary curves that don’t
196             # #give a cofactor? I’d need to figure out how to determine the
197             # #cofactor from the other parameters.
198             # my $curve_name = Crypt::Perl::ECDSA::EC::DB::get_curve_name_by_data(\%curve);
199             # my $params_hr = Crypt::Perl::ECDSA::EC::DB::get_curve_data_by_name($curve_name);
200             #
201             # @curve{'h', 'seed'} = @{$params}{'h', 'seed'};
202             # }
203              
204 138         993 return \%curve;
205             }
206              
207             #sub _get_db_params_or_undef {
208             # my ($curve_hr) = @_;
209             #
210             # my ($curve_name, $params_hr);
211             # try {
212             # $curve_name = Crypt::Perl::ECDSA::EC::DB::get_curve_name_by_data(\%curve);
213             # $params_hr = Crypt::Perl::ECDSA::EC::DB::get_curve_data_by_name($curve_name);
214             # }
215             # catch {
216             # if ( !try { $_->isa('Crypt::Perl::X::ECDSA::NoCurveForParameters') } ) {
217             # local $@ = $_;
218             # die;
219             # }
220             # };
221             #
222             # return $params_hr;
223             #}
224              
225             #----------------------------------------------------------------------
226              
227             sub _asn1 {
228 0     0     my ($class) = @_;
229              
230 0           return Crypt::Perl::ASN1->new()->prepare($class->ASN1_ECParameters())->find('ECParameters');
231             }
232              
233             1;