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