xref: /netbsd-src/crypto/external/bsd/openssl/dist/util/perl/OpenSSL/OID.pm (revision b0d1725196a7921d003d2c66a14f186abda4176b)
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