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