123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- # Copyright 2016-2020 The OpenSSL Project Authors. All Rights Reserved.
- #
- # Licensed under the Apache License 2.0 (the "License"). You may not use
- # this file except in compliance with the License. You can obtain a copy
- # in the file LICENSE in the source distribution or at
- # https://www.openssl.org/source/license.html
- # Author note: this is originally RL::ASN1::OID,
- # repurposed by the author for OpenSSL use.
- package OpenSSL::OID;
- use 5.10.0;
- use strict;
- use warnings;
- use Carp;
- use Exporter;
- use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
- @ISA = qw(Exporter);
- @EXPORT = qw(parse_oid encode_oid register_oid
- registered_oid_arcs registered_oid_leaves);
- @EXPORT_OK = qw(encode_oid_nums);
- # Unfortunately, the pairwise List::Util functionality came with perl
- # v5.19.3, and I want to target absolute compatibility with perl 5.10
- # and up. That means I have to implement quick pairwise functions here.
- #use List::Util;
- sub _pairs (@);
- sub _pairmap (&@);
- =head1 NAME
- OpenSSL::OID - an OBJECT IDENTIFIER parser / encoder
- =head1 VERSION
- Version 0.1
- =cut
- our $VERSION = '0.1';
- =head1 SYNOPSIS
- use OpenSSL::OID;
- # This gives the array ( 1 2 840 113549 1 1 )
- my @nums = parse_oid('{ pkcs-1 1 }');
- # This gives the array of DER encoded bytes for the OID, i.e.
- # ( 42, 134, 72, 134, 247, 13, 1, 1 )
- my @bytes = encode_oid('{ pkcs-1 1 }');
- # This registers a name with an OID. It's saved internally and
- # serves as repository of names for further parsing, such as 'pkcs-1'
- # in the strings used above.
- register_object('pkcs-1', '{ pkcs 1 }');
- use OpenSSL::OID qw(:DEFAULT encode_oid_nums);
- # This does the same as encode_oid(), but takes the output of
- # parse_oid() as input.
- my @bytes = encode_oid_nums(@nums);
- =head1 EXPORT
- The functions parse_oid and encode_oid are exported by default.
- The function encode_oid_nums() can be exported explicitly.
- =cut
- ######## REGEXPS
- # ASN.1 object identifiers come in two forms: 1) the bracketed form
- #(referred to as ObjectIdentifierValue in X.690), 2) the dotted form
- #(referred to as XMLObjIdentifierValue in X.690)
- #
- # examples of 1 (these are all the OID for rsaEncrypted):
- #
- # { iso (1) 2 840 11349 1 1 }
- # { pkcs 1 1 }
- # { pkcs1 1 }
- #
- # examples of 2:
- #
- # 1.2.840.113549.1.1
- # pkcs.1.1
- # pkcs1.1
- #
- my $identifier_re = qr/[a-z](?:[-_A-Za-z0-9]*[A-Za-z0-9])?/;
- # The only difference between $objcomponent_re and $xmlobjcomponent_re is
- # the separator in the top branch. Each component is always parsed in two
- # groups, so we get a pair of values regardless. That's the reason for the
- # empty parentheses.
- # Because perl doesn't try to do an exhaustive try of every branch it rather
- # stops on the first that matches, we need to have them in order of longest
- # to shortest where there may be ambiguity.
- my $objcomponent_re = qr/(?|
- (${identifier_re}) \s* \((\d+)\)
- |
- (${identifier_re}) ()
- |
- ()(\d+)
- )/x;
- my $xmlobjcomponent_re = qr/(?|
- (${identifier_re}) \. \((\d+)\)
- |
- (${identifier_re}) ()
- |
- () (\d+)
- )/x;
- my $obj_re =
- qr/(?: \{ \s* (?: ${objcomponent_re} \s+ )* ${objcomponent_re} \s* \} )/x;
- my $xmlobj_re =
- qr/(?: (?: ${xmlobjcomponent_re} \. )* ${xmlobjcomponent_re} )/x;
- ######## NAME TO OID REPOSITORY
- # Recorded OIDs, to support things like '{ pkcs1 1 }'
- # Do note that we don't currently support relative OIDs
- #
- # The key is the identifier.
- #
- # The value is a hash, composed of:
- # type => 'arc' | 'leaf'
- # nums => [ LIST ]
- # Note that the |type| always starts as a 'leaf', and may change to an 'arc'
- # on the fly, as new OIDs are parsed.
- my %name2oid = ();
- ########
- =head1 SUBROUTINES/METHODS
- =over 4
- =item parse_oid()
- TBA
- =cut
- sub parse_oid {
- my $input = shift;
- croak "Invalid extra arguments" if (@_);
- # The components become a list of ( identifier, number ) pairs,
- # where they can also be the empty string if they are not present
- # in the input.
- my @components;
- if ($input =~ m/^\s*(${obj_re})\s*$/x) {
- my $oid = $1;
- @components = ( $oid =~ m/${objcomponent_re}\s*/g );
- } elsif ($input =~ m/^\s*(${xmlobj_re})\s*$/) {
- my $oid = $1;
- @components = ( $oid =~ m/${xmlobjcomponent_re}\.?/g );
- }
- croak "Invalid ASN.1 object '$input'" unless @components;
- die "Internal error when parsing '$input'"
- unless scalar(@components) % 2 == 0;
- # As we currently only support a name without number as first
- # component, the easiest is to have a direct look at it and
- # hack it.
- my @first = _pairmap {
- my ($a, $b) = @$_;
- return $b if $b ne '';
- return @{$name2oid{$a}->{nums}} if $a ne '' && defined $name2oid{$a};
- croak "Undefined identifier $a" if $a ne '';
- croak "Empty OID element (how's that possible?)";
- } ( @components[0..1] );
- my @numbers =
- (
- @first,
- _pairmap {
- my ($a, $b) = @$_;
- return $b if $b ne '';
- croak "Unsupported relative OID $a" if $a ne '';
- croak "Empty OID element (how's that possible?)";
- } @components[2..$#components]
- );
- # If the first component has an identifier and there are other
- # components following it, we change the type of that identifier
- # to 'arc'.
- if (scalar @components > 2
- && $components[0] ne ''
- && defined $name2oid{$components[0]}) {
- $name2oid{$components[0]}->{type} = 'arc';
- }
- return @numbers;
- }
- =item encode_oid()
- =cut
- # Forward declaration
- sub encode_oid_nums;
- sub encode_oid {
- return encode_oid_nums parse_oid @_;
- }
- =item register_oid()
- =cut
- sub register_oid {
- my $name = shift;
- my @nums = parse_oid @_;
- if (defined $name2oid{$name}) {
- my $str1 = join(',', @nums);
- my $str2 = join(',', @{$name2oid{$name}->{nums}});
- croak "Invalid redefinition of $name with different value"
- unless $str1 eq $str2;
- } else {
- $name2oid{$name} = { type => 'leaf', nums => [ @nums ] };
- }
- }
- =item registered_oid_arcs()
- =item registered_oid_leaves()
- =cut
- sub _registered_oids {
- my $type = shift;
- return grep { $name2oid{$_}->{type} eq $type } keys %name2oid;
- }
- sub registered_oid_arcs {
- return _registered_oids( 'arc' );
- }
- sub registered_oid_leaves {
- return _registered_oids( 'leaf' );
- }
- =item encode_oid_nums()
- =cut
- # Internal helper. It takes a numeric OID component and generates the
- # DER encoding for it.
- sub _gen_oid_bytes {
- my $num = shift;
- my $cnt = 0;
- return ( $num ) if $num < 128;
- return ( ( map { $_ | 0x80 } _gen_oid_bytes($num >> 7) ), $num & 0x7f );
- }
- sub encode_oid_nums {
- my @numbers = @_;
- croak 'Invalid OID values: ( ', join(', ', @numbers), ' )'
- if (scalar @numbers < 2
- || $numbers[0] < 0 || $numbers[0] > 2
- || $numbers[1] < 0 || $numbers[1] > 39);
- my $first = shift(@numbers) * 40 + shift(@numbers);
- @numbers = ( $first, map { _gen_oid_bytes($_) } @numbers );
- return @numbers;
- }
- =back
- =head1 AUTHOR
- Richard levitte, C<< <richard at levitte.org> >>
- =cut
- ######## Helpers
- sub _pairs (@) {
- croak "Odd number of arguments" if @_ & 1;
- my @pairlist = ();
- while (@_) {
- my $x = [ shift, shift ];
- push @pairlist, $x;
- }
- return @pairlist;
- }
- sub _pairmap (&@) {
- my $block = shift;
- map { $block->($_) } _pairs @_;
- }
- 1; # End of OpenSSL::OID
|