Home | History | Annotate | Line # | Download | only in OpenSSL
      1 # Copyright 2016-2020 The OpenSSL Project Authors. All Rights Reserved.
      2 #
      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
      7 
      8 # Author note: this is originally RL::ASN1::OID,
      9 # repurposed by the author for OpenSSL use.
     10 
     11 package OpenSSL::OID;
     12 
     13 use 5.10.0;
     14 use strict;
     15 use warnings;
     16 use Carp;
     17 
     18 use Exporter;
     19 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
     20 @ISA = qw(Exporter);
     21 @EXPORT = qw(parse_oid encode_oid register_oid
     22              registered_oid_arcs registered_oid_leaves);
     23 @EXPORT_OK = qw(encode_oid_nums);
     24 
     25 # Unfortunately, the pairwise List::Util functionality came with perl
     26 # v5.19.3, and I want to target absolute compatibility with perl 5.10
     27 # and up.  That means I have to implement quick pairwise functions here.
     28 
     29 #use List::Util;
     30 sub _pairs (@);
     31 sub _pairmap (&@);
     32 
     33 =head1 NAME
     34 
     35 OpenSSL::OID - an OBJECT IDENTIFIER parser / encoder
     36 
     37 =head1 VERSION
     38 
     39 Version 0.1
     40 
     41 =cut
     42 
     43 our $VERSION = '0.1';
     44 
     45 
     46 =head1 SYNOPSIS
     47 
     48     use OpenSSL::OID;
     49 
     50     # This gives the array ( 1 2 840 113549 1 1 )
     51     my @nums = parse_oid('{ pkcs-1 1 }');
     52 
     53     # This gives the array of DER encoded bytes for the OID, i.e.
     54     # ( 42, 134, 72, 134, 247, 13, 1, 1 )
     55     my @bytes = encode_oid('{ pkcs-1 1 }');
     56 
     57     # This registers a name with an OID.  It's saved internally and
     58     # serves as repository of names for further parsing, such as 'pkcs-1'
     59     # in the strings used above.
     60     register_object('pkcs-1', '{ pkcs 1 }');
     61 
     62 
     63     use OpenSSL::OID qw(:DEFAULT encode_oid_nums);
     64 
     65     # This does the same as encode_oid(), but takes the output of
     66     # parse_oid() as input.
     67     my @bytes = encode_oid_nums(@nums);
     68 
     69 =head1 EXPORT
     70 
     71 The functions parse_oid and encode_oid are exported by default.
     72 The function encode_oid_nums() can be exported explicitly.
     73 
     74 =cut
     75 
     76 ######## REGEXPS
     77 
     78 # ASN.1 object identifiers come in two forms: 1) the bracketed form
     79 #(referred to as ObjectIdentifierValue in X.690), 2) the dotted form
     80 #(referred to as XMLObjIdentifierValue in X.690)
     81 #
     82 # examples of 1 (these are all the OID for rsaEncrypted):
     83 #
     84 # { iso (1) 2 840 11349 1 1 }
     85 # { pkcs 1 1 }
     86 # { pkcs1 1 }
     87 #
     88 # examples of 2:
     89 #
     90 # 1.2.840.113549.1.1
     91 # pkcs.1.1
     92 # pkcs1.1
     93 #
     94 my $identifier_re = qr/[a-z](?:[-_A-Za-z0-9]*[A-Za-z0-9])?/;
     95 # The only difference between $objcomponent_re and $xmlobjcomponent_re is
     96 # the separator in the top branch.  Each component is always parsed in two
     97 # groups, so we get a pair of values regardless.  That's the reason for the
     98 # empty parentheses.
     99 # Because perl doesn't try to do an exhaustive try of every branch it rather
    100 # stops on the first that matches, we need to have them in order of longest
    101 # to shortest where there may be ambiguity.
    102 my $objcomponent_re = qr/(?|
    103                              (${identifier_re}) \s* \((\d+)\)
    104                          |
    105                              (${identifier_re}) ()
    106                          |
    107                              ()(\d+)
    108                          )/x;
    109 my $xmlobjcomponent_re = qr/(?|
    110                                 (${identifier_re}) \. \((\d+)\)
    111                             |
    112                                 (${identifier_re}) ()
    113                             |
    114                                 () (\d+)
    115                             )/x;
    116 
    117 my $obj_re =
    118     qr/(?: \{ \s* (?: ${objcomponent_re} \s+ )* ${objcomponent_re} \s* \} )/x;
    119 my $xmlobj_re =
    120     qr/(?: (?: ${xmlobjcomponent_re} \. )* ${xmlobjcomponent_re} )/x;
    121 
    122 ######## NAME TO OID REPOSITORY
    123 
    124 # Recorded OIDs, to support things like '{ pkcs1 1 }'
    125 # Do note that we don't currently support relative OIDs
    126 #
    127 # The key is the identifier.
    128 #
    129 # The value is a hash, composed of:
    130 # type => 'arc' | 'leaf'
    131 # nums => [ LIST ]
    132 # Note that the |type| always starts as a 'leaf', and may change to an 'arc'
    133 # on the fly, as new OIDs are parsed.
    134 my %name2oid = ();
    135 
    136 ########
    137 
    138 =head1 SUBROUTINES/METHODS
    139 
    140 =over 4
    141 
    142 =item parse_oid()
    143 
    144 TBA
    145 
    146 =cut
    147 
    148 sub parse_oid {
    149     my $input = shift;
    150 
    151     croak "Invalid extra arguments" if (@_);
    152 
    153     # The components become a list of ( identifier, number ) pairs,
    154     # where they can also be the empty string if they are not present
    155     # in the input.
    156     my @components;
    157     if ($input =~ m/^\s*(${obj_re})\s*$/x) {
    158         my $oid = $1;
    159         @components = ( $oid =~ m/${objcomponent_re}\s*/g );
    160     } elsif ($input =~ m/^\s*(${xmlobj_re})\s*$/) {
    161         my $oid = $1;
    162         @components = ( $oid =~ m/${xmlobjcomponent_re}\.?/g );
    163     }
    164 
    165     croak "Invalid ASN.1 object '$input'" unless @components;
    166     die "Internal error when parsing '$input'"
    167         unless scalar(@components) % 2 == 0;
    168 
    169     # As we currently only support a name without number as first
    170     # component, the easiest is to have a direct look at it and
    171     # hack it.
    172     my @first = _pairmap {
    173         my ($a, $b) = @$_;
    174         return $b if $b ne '';
    175         return @{$name2oid{$a}->{nums}} if $a ne '' && defined $name2oid{$a};
    176         croak "Undefined identifier $a" if $a ne '';
    177         croak "Empty OID element (how's that possible?)";
    178     } ( @components[0..1] );
    179 
    180     my @numbers =
    181         (
    182          @first,
    183          _pairmap {
    184              my ($a, $b) = @$_;
    185              return $b if $b ne '';
    186              croak "Unsupported relative OID $a" if $a ne '';
    187              croak "Empty OID element (how's that possible?)";
    188          } @components[2..$#components]
    189         );
    190 
    191     # If the first component has an identifier and there are other
    192     # components following it, we change the type of that identifier
    193     # to 'arc'.
    194     if (scalar @components > 2
    195         && $components[0] ne ''
    196         && defined $name2oid{$components[0]}) {
    197         $name2oid{$components[0]}->{type} = 'arc';
    198     }
    199 
    200     return @numbers;
    201 }
    202 
    203 =item encode_oid()
    204 
    205 =cut
    206 
    207 # Forward declaration
    208 sub encode_oid_nums;
    209 sub encode_oid {
    210     return encode_oid_nums parse_oid @_;
    211 }
    212 
    213 =item register_oid()
    214 
    215 =cut
    216 
    217 sub register_oid {
    218     my $name = shift;
    219     my @nums = parse_oid @_;
    220 
    221     if (defined $name2oid{$name}) {
    222         my $str1 = join(',', @nums);
    223         my $str2 = join(',', @{$name2oid{$name}->{nums}});
    224 
    225         croak "Invalid redefinition of $name with different value"
    226             unless $str1 eq $str2;
    227     } else {
    228         $name2oid{$name} = { type => 'leaf', nums => [ @nums ] };
    229     }
    230 }
    231 
    232 =item registered_oid_arcs()
    233 
    234 =item registered_oid_leaves()
    235 
    236 =cut
    237 
    238 sub _registered_oids {
    239     my $type = shift;
    240 
    241     return grep { $name2oid{$_}->{type} eq $type } keys %name2oid;
    242 }
    243 
    244 sub registered_oid_arcs {
    245     return _registered_oids( 'arc' );
    246 }
    247 
    248 sub registered_oid_leaves {
    249     return _registered_oids( 'leaf' );
    250 }
    251 
    252 =item encode_oid_nums()
    253 
    254 =cut
    255 
    256 # Internal helper.  It takes a numeric OID component and generates the
    257 # DER encoding for it.
    258 sub _gen_oid_bytes {
    259     my $num = shift;
    260     my $cnt = 0;
    261 
    262     return ( $num ) if $num < 128;
    263     return ( ( map { $_ | 0x80 } _gen_oid_bytes($num >> 7) ), $num & 0x7f );
    264 }
    265 
    266 sub encode_oid_nums {
    267     my @numbers = @_;
    268 
    269     croak 'Invalid OID values: ( ', join(', ', @numbers), ' )'
    270         if (scalar @numbers < 2
    271             || $numbers[0] < 0 || $numbers[0] > 2
    272             || $numbers[1] < 0 || $numbers[1] > 39);
    273 
    274     my $first = shift(@numbers) * 40 + shift(@numbers);
    275     @numbers = ( $first, map { _gen_oid_bytes($_) } @numbers );
    276 
    277     return @numbers;
    278 }
    279 
    280 =back
    281 
    282 =head1 AUTHOR
    283 
    284 Richard levitte, C<< <richard at levitte.org> >>
    285 
    286 =cut
    287 
    288 ######## Helpers
    289 
    290 sub _pairs (@) {
    291     croak "Odd number of arguments" if @_ & 1;
    292 
    293     my @pairlist = ();
    294 
    295     while (@_) {
    296         my $x = [ shift, shift ];
    297         push @pairlist, $x;
    298     }
    299     return @pairlist;
    300 }
    301 
    302 sub _pairmap (&@) {
    303     my $block = shift;
    304     map { $block->($_) } _pairs @_;
    305 }
    306 
    307 1; # End of OpenSSL::OID
    308