File Coverage

lib/Crypt/Perl/PKCS10.pm
Criterion Covered Total %
statement 74 79 93.6
branch 10 14 71.4
condition 2 6 33.3
subroutine 15 15 100.0
pod 1 2 50.0
total 102 116 87.9


line stmt bran cond sub pod time code
1             package Crypt::Perl::PKCS10;
2              
3 1     1   328 use strict;
  1         2  
  1         21  
4 1     1   3 use warnings;
  1         2  
  1         27  
5              
6             =encoding utf-8
7              
8             =head1 NAME
9              
10             Crypt::Perl::PKCS10 - Certificate Signing Request (CSR) creation
11              
12             =head1 SYNOPSIS
13              
14             my $pkcs10 = Crypt::Perl::PKCS10->new(
15              
16             key => $private_key_obj,
17              
18             subject => [
19             commonName => 'foo.com',
20             localityName => 'somewhere',
21             #...
22             ],
23             attributes => [
24             [ 'extensionRequest',
25             [ 'subjectAltName',
26             [ dNSName => 'foo.com' ],
27             [ dNSName => 'bar.com' ],
28             ],
29             ],
30             ],
31             );
32              
33             my $der = $pkcs10->to_der();
34             my $pem = $pkcs10->to_pem();
35              
36             =head1 DESCRIPTION
37              
38             This module is for creation of (PKCS #10) certificate signing requests (CSRs).
39             Right now it supports only a
40             subset of what L can create; however, it’s
41             useful enough for use with many certificate authorities, including
42             L services like
43             L.
44              
45             It’s also a good deal easier to use!
46              
47             I believe this is the only L module that
48             can create CSRs for RSA, ECDSA, and Ed25519 keys. Other encryption schemes
49             would not be difficult to integrate—but do any CAs accept them?
50              
51             =head1 ECDSA KEY FORMAT
52              
53             After a brief flirtation (cf. v0.13) with producing ECDSA-signed CSRs using
54             explicit curve parameters, this module produces CSRs using B curves.
55             Certificate authorities seem to prefer this format—which makes sense since
56             they only allow certain curves in the first place.
57              
58             =head1 SIGNATURE DIGEST ALGORITHMS
59              
60             The signature digest algorithm is
61             determined based on the passed-in key: for RSA it’s always SHA-512, and for
62             ECDSA it’s the strongest SHA digest algorithm that the key allows
63             (e.g., SHA-224 for a 239-bit key, etc.)
64              
65             If you need additional flexibility, let me know.
66              
67             (Note that Ed25519 signs an entire document rather than a digest.)
68              
69             =head1 CLASS METHODS
70              
71             =head2 new( NAME => VALUE, ... );
72              
73             Create an instance of this class. Parameters are:
74              
75             =over 4
76              
77             =item * C - An instance of C,
78             C, or C.
79             If you’ve got a DER- or PEM-encoded key string, use L
80             (included in this distribution) to create an appropriate object.
81              
82             =item * C - An array reference of arguments into
83             L’s constructor.
84              
85             =item * C - An array reference of arguments into
86             L’s constructor.
87              
88             =back
89              
90             =head1 TODO
91              
92             Let me know what features you would find useful, ideally with
93             a representative sample CSR that demonstrates the requested feature.
94             (Or, better yet, send me a pull request!)
95              
96             =head1 SEE ALSO
97              
98             =over 4
99              
100             =item * L - Parse CSRs, in pure Perl.
101              
102             =item * L - Create CSRs using OpenSSL via XS.
103             Currently this only seems to support RSA.
104              
105             =back
106              
107             =cut
108              
109 1     1   3 use Crypt::Perl::ASN1 ();
  1         2  
  1         9  
110 1     1   348 use Crypt::Perl::ASN1::Signatures ();
  1         2  
  1         15  
111 1     1   323 use Crypt::Perl::PKCS10::Attributes ();
  1         2  
  1         14  
112 1     1   5 use Crypt::Perl::PKCS10::Attributes ();
  1         1  
  1         10  
113 1     1   4 use Crypt::Perl::X509::Name ();
  1         1  
  1         9  
114 1     1   3 use Crypt::Perl::X ();
  1         2  
  1         13  
115              
116 1     1   3 use parent qw( Crypt::Perl::ASN1::Encodee );
  1         2  
  1         2  
117              
118             *to_der = __PACKAGE__->can('encode');
119              
120             sub to_pem {
121 9     9 0 6492 my ($self) = @_;
122              
123 9         58 require Crypt::Format;
124 9         152 return Crypt::Format::der2pem( $self->to_der(), 'CERTIFICATE REQUEST' );
125             }
126              
127 1     1   108 use constant ASN1 => <
  1         2  
  1         38  
128             AlgorithmIdentifier ::= SEQUENCE {
129             algorithm OBJECT IDENTIFIER,
130             parameters ANY
131             }
132              
133             CertificationRequestInfo ::= SEQUENCE {
134             version INTEGER,
135             subject ANY,
136             subjectPKInfo ANY,
137             attributes ANY OPTIONAL
138             }
139              
140             CertificationRequest ::= SEQUENCE {
141             certificationRequestInfo CertificationRequestInfo,
142             signatureAlgorithm AlgorithmIdentifier,
143             signature BIT STRING
144             }
145             END
146              
147 1     1   4 use constant asn1_macro => 'CertificationRequest';
  1         1  
  1         523  
148              
149             sub new {
150 9     9 1 516 my ($class, %opts) = @_;
151              
152 9         49 my ($key, $attrs, $subject) = @opts{'key', 'attributes', 'subject'};
153              
154 9         176 $subject = Crypt::Perl::X509::Name->new( @$subject );
155 9         179 $attrs = Crypt::Perl::PKCS10::Attributes->new( @$attrs );
156              
157 9         57 my $self = {
158             _key => $key,
159             _subject => $subject,
160             _attributes => $attrs,
161             };
162              
163 9         56 return bless $self, $class;
164             }
165              
166             sub _encode_params {
167 9     9   22 my ($self) = @_;
168              
169 9         29 my $key = $self->{'_key'};
170              
171 9         16 my ($pk_der);
172 9         21 my ($sig_alg, $sig_param, $sig_func);
173              
174 9 100       81 if ($key->isa('Crypt::Perl::ECDSA::PrivateKey')) {
    50          
    0          
175 7         44 require Digest::SHA;
176              
177 7         43 my $bits = $key->max_sign_bits();
178              
179 7 50       9819 if ($bits < 224) {
    100          
    100          
    100          
180 0         0 die Crypt::Perl::X::create('Generic', "This key is too weak ($bits bits) to make a secure PKCS #10 CSR.");
181             }
182             elsif ($bits < 256) {
183 2         4 $bits = 224;
184             }
185             elsif ($bits < 384) {
186 2         11 $bits = 256;
187             }
188             elsif ($bits < 512) {
189 1         12 $bits = 384;
190             }
191             else {
192 2         24 $bits = 512;
193             }
194              
195 7         26 $sig_alg = "ecdsa-with-SHA$bits";
196              
197             $sig_func = sub {
198 7     7   27 my ($key, $msg) = @_;
199              
200 7         241 return $key->sign( Digest::SHA->can("sha$bits")->($msg) );
201 7         91 };
202              
203 7         90 $pk_der = $key->get_public_key()->to_der_with_curve_name();
204             }
205             elsif ($key->isa('Crypt::Perl::RSA::PrivateKey')) {
206 2         11 require Digest::SHA;
207              
208 2         12 $sig_alg = 'sha512WithRSAEncryption';
209 2         6 $sig_param = q<>;
210 2         11 $sig_func = $key->can('sign_RS512');
211              
212 2         13 $pk_der = $key->to_subject_public_der();
213             }
214             elsif ($key->isa('Crypt::Perl::Ed25519::PrivateKey')) {
215 0         0 $sig_alg = 'ed25519';
216 0         0 $sig_func = $key->can('sign');
217 0         0 $pk_der = $key->get_public_key()->to_der();
218             }
219             else {
220 0         0 die Crypt::Perl::X::create('Generic', "Key ($key) is not a recognized private key class instance!");
221             }
222              
223 9   33     3099 $sig_alg = $Crypt::Perl::ASN1::Signatures::OID{$sig_alg} || do {
224             die Crypt::Perl::X::create('Generic', "Unrecognized signature algorithm OID: “$sig_alg”");
225             };
226              
227 9         58 my $asn1_reqinfo = Crypt::Perl::ASN1->new()->prepare( $self->ASN1() );
228 9         48 $asn1_reqinfo = $asn1_reqinfo->find('CertificationRequestInfo');
229              
230 9         186 my $subj_enc = $self->{'_subject'}->encode();
231              
232 9         982 my $attr_enc = $self->{'_attributes'}->encode();
233              
234             #We need the attributes not to be a SET, but CONTEXT [0].
235             #That means the first byte needs to be 0xa0, not 0x31.
236             #This is a detail germane to the PKCS10 structure, not to the
237             #Attributes itself (right??), so it makes sense to do the change here
238             #rather than to put “[0] SET” into the ASN1 template for Attributes.
239             #
240             #“use bytes” is not necessary because we know the first character is
241             #0x31, which came from Convert::ASN1.
242 9         2209 substr($attr_enc, 0, 1) = chr 0xa0;
243              
244 9         55 my %reqinfo = (
245             version => 0,
246             subject => $subj_enc,
247             subjectPKInfo => $pk_der,
248             attributes => $attr_enc,
249             );
250              
251 9         33 my $reqinfo_enc = $asn1_reqinfo->encode(\%reqinfo);
252              
253 9         1229 my $signature = $sig_func->( $key, $reqinfo_enc );
254              
255             return {
256 9   33     325185 certificationRequestInfo => \%reqinfo,
257             signatureAlgorithm => {
258             algorithm => $sig_alg,
259             parameters => $sig_param || Crypt::Perl::ASN1::NULL(),
260             },
261             signature => $signature,
262             };
263             }
264              
265             1;