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