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