File Coverage

lib/Crypt/Perl/PKCS10.pm
Criterion Covered Total %
statement 77 79 97.4
branch 10 12 83.3
condition 2 6 33.3
subroutine 17 17 100.0
pod 1 2 50.0
total 107 116 92.2


line stmt bran cond sub pod time code
1             package Crypt::Perl::PKCS10;
2              
3 1     1   308 use strict;
  1         1  
  1         18  
4 1     1   3 use warnings;
  1         1  
  1         23  
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 => $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 either RSA or ECDSA keys. Other encryption schemes would
49             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             =head1 CLASS METHODS
68              
69             =head2 new( NAME => VALUE, ... );
70              
71             Create an instance of this class. Parameters are:
72              
73             =over 4
74              
75             =item * C - An instance of either
76             C or C.
77             If you’ve got a DER- or PEM-encoded key string, use L
78             (included in this distribution) to create an appropriate object.
79              
80             =item * C - An array reference of arguments into
81             L’s constructor.
82              
83             =item * C - An array reference of arguments into
84             L’s constructor.
85              
86             =back
87              
88             =head1 TODO
89              
90             Let me know what features you would find useful, ideally with
91             a representative sample CSR that demonstrates the requested feature.
92             (Or, better yet, send me a pull request!)
93              
94             =head1 SEE ALSO
95              
96             =over 4
97              
98             =item * L - Parse CSRs, in pure Perl.
99              
100             =item * L - Create CSRs using OpenSSL via XS.
101             Currently this only seems to support RSA.
102              
103             =back
104              
105             =cut
106              
107 1     1   3 use Crypt::Format ();
  1         1  
  1         8  
108 1     1   366 use Digest::SHA ();
  1         1802  
  1         17  
109              
110 1     1   4 use Crypt::Perl::ASN1 ();
  1         1  
  1         9  
111 1     1   301 use Crypt::Perl::ASN1::Signatures ();
  1         2  
  1         14  
112 1     1   345 use Crypt::Perl::PKCS10::Attributes ();
  1         1  
  1         14  
113 1     1   4 use Crypt::Perl::PKCS10::Attributes ();
  1         1  
  1         8  
114 1     1   3 use Crypt::Perl::X509::Name ();
  1         1  
  1         8  
115 1     1   2 use Crypt::Perl::X ();
  1         2  
  1         11  
116              
117 1     1   3 use parent qw( Crypt::Perl::ASN1::Encodee );
  1         1  
  1         3  
118              
119             *to_der = __PACKAGE__->can('encode');
120              
121             sub to_pem {
122 9     9 0 5933 my ($self) = @_;
123              
124 9         95 return Crypt::Format::der2pem( $self->to_der(), 'CERTIFICATE REQUEST' );
125             }
126              
127 1     1   101 use constant ASN1 => <
  1         1  
  1         33  
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         373  
148              
149             sub new {
150 9     9 1 513 my ($class, %opts) = @_;
151              
152 9         48 my ($key, $attrs, $subject) = @opts{'key', 'attributes', 'subject'};
153              
154 9         186 $subject = Crypt::Perl::X509::Name->new( @$subject );
155 9         172 $attrs = Crypt::Perl::PKCS10::Attributes->new( @$attrs );
156              
157 9         54 my $self = {
158             _key => $key,
159             _subject => $subject,
160             _attributes => $attrs,
161             };
162              
163 9         38 return bless $self, $class;
164             }
165              
166             sub _encode_params {
167 9     9   22 my ($self) = @_;
168              
169 9         26 my $key = $self->{'_key'};
170              
171 9         13 my ($pk_der);
172 9         22 my ($sig_alg, $sig_param, $sig_func);
173              
174 9 100       79 if ($key->isa('Crypt::Perl::ECDSA::PrivateKey')) {
    50          
175              
176 7         58 my $bits = $key->max_sign_bits();
177              
178 7 50       8400 if ($bits < 224) {
    100          
    100          
    100          
179 0         0 die Crypt::Perl::X::create('Generic', "This key is too weak ($bits bits) to make a secure PKCS #10 CSR.");
180             }
181             elsif ($bits < 256) {
182 2         7 $bits = 224;
183             }
184             elsif ($bits < 384) {
185 2         12 $bits = 256;
186             }
187             elsif ($bits < 512) {
188 1         11 $bits = 384;
189             }
190             else {
191 2         15 $bits = 512;
192             }
193              
194 7         28 $sig_alg = "ecdsa-with-SHA$bits";
195              
196             $sig_func = sub {
197 7     7   23 my ($key, $msg) = @_;
198              
199 7         279 return $key->sign( Digest::SHA->can("sha$bits")->($msg) );
200 7         104 };
201              
202 7         69 $pk_der = $key->get_public_key()->to_der_with_curve_name();
203             }
204             elsif ($key->isa('Crypt::Perl::RSA::PrivateKey')) {
205 2         15 $sig_alg = 'sha512WithRSAEncryption';
206 2         5 $sig_param = q<>;
207 2         14 $sig_func = $key->can('sign_RS512');
208              
209 2         14 $pk_der = $key->to_subject_public_der();
210             }
211             else {
212 0         0 die Crypt::Perl::X::create('Generic', "Key ($key) is not a recognized private key class instance!");
213             }
214              
215 9   33     2824 $sig_alg = $Crypt::Perl::ASN1::Signatures::OID{$sig_alg} || do {
216             die Crypt::Perl::X::create('Generic', "Unrecognized signature algorithm OID: “$sig_alg”");
217             };
218              
219 9         46 my $asn1_reqinfo = Crypt::Perl::ASN1->new()->prepare( $self->ASN1() );
220 9         40 $asn1_reqinfo = $asn1_reqinfo->find('CertificationRequestInfo');
221              
222 9         172 my $subj_enc = $self->{'_subject'}->encode();
223              
224 9         891 my $attr_enc = $self->{'_attributes'}->encode();
225              
226             #We need the attributes not to be a SET, but CONTEXT [0].
227             #That means the first byte needs to be 0xa0, not 0x31.
228             #This is a detail germane to the PKCS10 structure, not to the
229             #Attributes itself (right??), so it makes sense to do the change here
230             #rather than to put “[0] SET” into the ASN1 template for Attributes.
231             #
232             #“use bytes” is not necessary because we know the first character is
233             #0x31, which came from Convert::ASN1.
234 9         2206 substr($attr_enc, 0, 1) = chr 0xa0;
235              
236 9         57 my %reqinfo = (
237             version => 0,
238             subject => $subj_enc,
239             subjectPKInfo => $pk_der,
240             attributes => $attr_enc,
241             );
242              
243 9         190 my $reqinfo_enc = $asn1_reqinfo->encode(\%reqinfo);
244              
245 9         1317 my $signature = $sig_func->( $key, $reqinfo_enc );
246              
247             return {
248 9   33     287882 certificationRequestInfo => \%reqinfo,
249             signatureAlgorithm => {
250             algorithm => $sig_alg,
251             parameters => $sig_param || Crypt::Perl::ASN1::NULL(),
252             },
253             signature => $signature,
254             };
255             }
256              
257             1;