1 # Copyright 2016-2019 The OpenSSL Project Authors. All Rights Reserved.
3 # Licensed under the Apache License 2.0 (the "License"). You may not use
4 # this file except in compliance with the License. You can obtain a copy
5 # in the file LICENSE in the source distribution or at
6 # https://www.openssl.org/source/license.html
8 # Author note: this is originally RL::ASN1::OID,
9 # repurposed by the author for OpenSSL use.
19 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
21 @EXPORT = qw(parse_oid encode_oid register_oid
22 registered_oid_arcs registered_oid_leaves);
23 @EXPORT_OK = qw(encode_oid_nums);
29 OpenSSL::OID - an OBJECT IDENTIFIER parser / encoder
44 # This gives the array ( 1 2 840 113549 1 1 )
45 my @nums = parse_oid('{ pkcs-1 1 }');
47 # This gives the array of DER encoded bytes for the OID, i.e.
48 # ( 42, 134, 72, 134, 247, 13, 1, 1 )
49 my @bytes = encode_oid('{ pkcs-1 1 }');
51 # This registers a name with an OID. It's saved internally and
52 # serves as repository of names for further parsing, such as 'pkcs-1'
53 # in the strings used above.
54 register_object('pkcs-1', '{ pkcs 1 }');
57 use OpenSSL::OID qw(:DEFAULT encode_oid_nums);
59 # This does the same as encode_oid(), but takes the output of
60 # parse_oid() as input.
61 my @bytes = encode_oid_nums(@nums);
65 The functions parse_oid and encode_oid are exported by default.
66 The function encode_oid_nums() can be exported explicitly.
72 # ASN.1 object identifiers come in two forms: 1) the bracketed form
73 #(referred to as ObjectIdentifierValue in X.690), 2) the dotted form
74 #(referred to as XMLObjIdentifierValue in X.690)
76 # examples of 1 (these are all the OID for rsaEncrypted):
78 # { iso (1) 2 840 11349 1 1 }
88 my $identifier_re = qr/[a-z](?:[-_A-Za-z0-9]*[A-Za-z0-9])?/;
89 # The only difference between $objcomponent_re and $xmlobjcomponent_re is
90 # the separator in the top branch. Each component is always parsed in two
91 # groups, so we get a pair of values regardless. That's the reason for the
93 # Because perl doesn't try to do an exhaustive try of every branch it rather
94 # stops on the first that matches, we need to have them in order of longest
95 # to shortest where there may be ambiguity.
96 my $objcomponent_re = qr/(?|
97 (${identifier_re}) \s* \((\d+)\)
103 my $xmlobjcomponent_re = qr/(?|
104 (${identifier_re}) \. \((\d+)\)
106 (${identifier_re}) ()
112 qr/(?: \{ \s* (?: ${objcomponent_re} \s+ )* ${objcomponent_re} \s* \} )/x;
114 qr/(?: (?: ${xmlobjcomponent_re} \. )* ${xmlobjcomponent_re} )/x;
116 ######## NAME TO OID REPOSITORY
118 # Recorded OIDs, to support things like '{ pkcs1 1 }'
119 # Do note that we don't currently support relative OIDs
121 # The key is the identifier.
123 # The value is a hash, composed of:
124 # type => 'arc' | 'leaf'
126 # Note that the |type| always starts as a 'leaf', and may change to an 'arc'
127 # on the fly, as new OIDs are parsed.
132 =head1 SUBROUTINES/METHODS
145 croak "Invalid extra arguments" if (@_);
147 # The components become a list of ( identifier, number ) pairs,
148 # where they can also be the empty string if they are not present
151 if ($input =~ m/^\s*(${obj_re})\s*$/x) {
153 @components = ( $oid =~ m/${objcomponent_re}\s*/g );
154 } elsif ($input =~ m/^\s*(${xmlobj_re})\s*$/) {
156 @components = ( $oid =~ m/${xmlobjcomponent_re}\.?/g );
159 croak "Invalid ASN.1 object '$input'" unless @components;
160 die "Internal error when parsing '$input'"
161 unless scalar(@components) % 2 == 0;
163 # As we currently only support a name without number as first
164 # component, the easiest is to have a direct look at it and
166 my @first = List::Util::pairmap {
167 return $b if $b ne '';
168 return @{$name2oid{$a}->{nums}} if $a ne '' && defined $name2oid{$a};
169 croak "Undefined identifier $a" if $a ne '';
170 croak "Empty OID element (how's that possible?)";
171 } ( @components[0..1] );
176 List::Util::pairmap {
177 return $b if $b ne '';
178 croak "Unsupported relative OID $a" if $a ne '';
179 croak "Empty OID element (how's that possible?)";
180 } @components[2..$#components]
183 # If the first component has an identifier and there are other
184 # components following it, we change the type of that identifier
186 if (scalar @components > 2
187 && $components[0] ne ''
188 && defined $name2oid{$components[0]}) {
189 $name2oid{$components[0]}->{type} = 'arc';
199 # Forward declaration
202 return encode_oid_nums parse_oid @_;
211 my @nums = parse_oid @_;
213 if (defined $name2oid{$name}) {
214 my $str1 = join(',', @nums);
215 my $str2 = join(',', @{$name2oid{$name}->{nums}});
217 croak "Invalid redefinition of $name with different value"
218 unless $str1 eq $str2;
220 $name2oid{$name} = { type => 'leaf', nums => [ @nums ] };
224 =item registered_oid_arcs()
226 =item registered_oid_leaves()
230 sub _registered_oids {
233 return grep { $name2oid{$_}->{type} eq $type } keys %name2oid;
236 sub registered_oid_arcs {
237 return _registered_oids( 'arc' );
240 sub registered_oid_leaves {
241 return _registered_oids( 'leaf' );
244 =item encode_oid_nums()
248 # Internal helper. It takes a numeric OID component and generates the
249 # DER encoding for it.
254 return ( $num ) if $num < 128;
255 return ( ( map { $_ | 0x80 } _gen_oid_bytes($num >> 7) ), $num & 0x7f );
258 sub encode_oid_nums {
261 croak 'Invalid OID values: ( ', join(', ', @numbers), ' )'
262 if (scalar @numbers < 2
263 || $numbers[0] < 0 || $numbers[0] > 2
264 || $numbers[1] < 0 || $numbers[1] > 39);
266 my $first = shift(@numbers) * 40 + shift(@numbers);
267 @numbers = ( $first, map { _gen_oid_bytes($_) } @numbers );
276 Richard levitte, C<< <richard at levitte.org> >>
280 ######## UNIT TESTING
285 # Order is important, so we make it a pairwise list
288 'pkcs' => '1.2.840.113549',
289 'pkcs-1' => 'pkcs.1',
294 ' 1.2.840.113549.1.1 ' => [ 42, 134, 72, 134, 247, 13, 1, 1 ],
295 'pkcs.1.1' => [ 42, 134, 72, 134, 247, 13, 1, 1 ],
296 'pkcs-1.1' => [ 42, 134, 72, 134, 247, 13, 1, 1 ],
297 ' { iso (1) 2 840 113549 1 1 } ' => [ 42, 134, 72, 134, 247, 13, 1, 1 ],
298 '{ pkcs 1 1 } ' => [ 42, 134, 72, 134, 247, 13, 1, 1 ],
299 '{pkcs-1 1 }' => [ 42, 134, 72, 134, 247, 13, 1, 1 ],
303 ' { 1.2.840.113549.1.1 } ',
307 scalar ( @predefined ) / 2
308 + scalar ( keys %good_cases )
311 note 'Predefine a few names OIDs';
312 foreach my $pair ( List::Util::pairs @predefined ) {
313 ok( defined eval { register_oid(@$pair) },
314 "Registering $pair->[0] => $pair->[1]" );
318 foreach ( keys %good_cases ) {
319 subtest "Checking '$_'" => sub {
326 ok( scalar (@l = eval { parse_oid $oid }) > 0,
329 ok( scalar (@e = eval { encode_oid_nums @l }) > 0,
330 "Encoding via encode_oid_nums()" );
332 is_deeply(\@e, $good_cases{$oid}, "Checking encoding");
333 note "'$oid' => ", join(', ', @e) if @e;
335 ok( scalar (@e = eval { encode_oid $oid }) > 0,
336 "Encoding directly" );
338 is_deeply(\@e, $good_cases{$oid}, "Checking encoding");
339 note "'$oid' => ", join(', ', @e) if @e;
345 foreach ( @bad_cases ) {
346 subtest "Checking '$_'" => sub {
353 ok( scalar (@l = eval { parse_oid $oid }) == 0,
356 ok( scalar (@e = eval { encode_oid_nums @l }) == 0,
359 note "'$oid' => ", join(', ', @e) if @e;
365 1; # End of OpenSSL::OID