File Coverage

lib/Crypt/Perl/PKCS10.pm
Criterion Covered Total %
statement 75 80 93.7
branch 10 14 71.4
condition 2 6 33.3
subroutine 15 15 100.0
pod 1 2 50.0
total 103 117 88.0


line stmt bran cond sub pod time code
1             package Crypt::Perl::PKCS10;
2              
3 1     1   491 use strict;
  1         2  
  1         29  
4 1     1   5 use warnings;
  1         1  
  1         36  
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   5 use Crypt::Perl::ASN1 ();
  1         1  
  1         17  
110 1     1   478 use Crypt::Perl::ASN1::Signatures ();
  1         2  
  1         19  
111 1     1   462 use Crypt::Perl::PKCS10::Attributes ();
  1         3  
  1         19  
112 1     1   6 use Crypt::Perl::PKCS10::Attributes ();
  1         2  
  1         15  
113 1     1   4 use Crypt::Perl::X509::Name ();
  1         2  
  1         12  
114 1     1   4 use Crypt::Perl::X ();
  1         2  
  1         18  
115              
116 1     1   4 use parent qw( Crypt::Perl::ASN1::Encodee );
  1         13  
  1         5  
117              
118             *to_der = __PACKAGE__->can('encode');
119              
120             sub to_pem {
121 9     9 0 8286 my ($self) = @_;
122              
123 9         73 require Crypt::Format;
124 9         151 return Crypt::Format::der2pem( $self->to_der(), 'CERTIFICATE REQUEST' );
125             }
126              
127 1     1   154 use constant ASN1 => <
  1         2  
  1         52  
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   6 use constant asn1_macro => 'CertificationRequest';
  1         2  
  1         667  
148              
149             sub new {
150 9     9 1 662 my ($class, %opts) = @_;
151              
152 9         79 my ($key, $attrs, $subject) = @opts{'key', 'attributes', 'subject'};
153              
154 9         340 $subject = Crypt::Perl::X509::Name->new( @$subject );
155 9         249 $attrs = Crypt::Perl::PKCS10::Attributes->new( @$attrs );
156              
157 9         73 my $self = {
158             _key => $key,
159             _subject => $subject,
160             _attributes => $attrs,
161             };
162              
163 9         65 return bless $self, $class;
164             }
165              
166             sub _encode_params {
167 9     9   42 my ($self) = @_;
168              
169 9         44 my $key = $self->{'_key'};
170              
171 9         20 my ($pk_der);
172 9         60 my ($sig_alg, $sig_param, $sig_func);
173              
174 9 100       128 if ($key->isa('Crypt::Perl::ECDSA::PrivateKey')) {
    50          
    0          
175 7         56 require Digest::SHA;
176              
177 7         74 my $bits = $key->max_sign_bits();
178              
179 7 50       359 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         5 $bits = 224;
184             }
185             elsif ($bits < 384) {
186 2         16 $bits = 256;
187             }
188             elsif ($bits < 512) {
189 1         20 $bits = 384;
190             }
191             else {
192 2         20 $bits = 512;
193             }
194              
195 7         32 $sig_alg = "ecdsa-with-SHA$bits";
196              
197 7         24 my $fn = "sign_sha$bits";
198              
199             $sig_func = sub {
200 7     7   28 my ($key, $msg) = @_;
201              
202 7         119 return $key->$fn($msg);
203 7         99 };
204              
205 7         87 $pk_der = $key->get_public_key()->to_der_with_curve_name();
206             }
207             elsif ($key->isa('Crypt::Perl::RSA::PrivateKey')) {
208 2         16 require Digest::SHA;
209              
210 2         10 $sig_alg = 'sha512WithRSAEncryption';
211 2         5 $sig_param = q<>;
212 2         16 $sig_func = $key->can('sign_RS512');
213              
214 2         19 $pk_der = $key->to_subject_public_der();
215             }
216             elsif ($key->isa('Crypt::Perl::Ed25519::PrivateKey')) {
217 0         0 $sig_alg = 'ed25519';
218 0         0 $sig_func = $key->can('sign');
219 0         0 $pk_der = $key->get_public_key()->to_der();
220             }
221             else {
222 0         0 die Crypt::Perl::X::create('Generic', "Key ($key) is not a recognized private key class instance!");
223             }
224              
225 9   33     3967 $sig_alg = $Crypt::Perl::ASN1::Signatures::OID{$sig_alg} || do {
226             die Crypt::Perl::X::create('Generic', "Unrecognized signature algorithm OID: “$sig_alg”");
227             };
228              
229 9         63 my $asn1_reqinfo = Crypt::Perl::ASN1->new()->prepare( $self->ASN1() );
230 9         55 $asn1_reqinfo = $asn1_reqinfo->find('CertificationRequestInfo');
231              
232 9         274 my $subj_enc = $self->{'_subject'}->encode();
233              
234 9         1261 my $attr_enc = $self->{'_attributes'}->encode();
235              
236             #We need the attributes not to be a SET, but CONTEXT [0].
237             #That means the first byte needs to be 0xa0, not 0x31.
238             #This is a detail germane to the PKCS10 structure, not to the
239             #Attributes itself (right??), so it makes sense to do the change here
240             #rather than to put “[0] SET” into the ASN1 template for Attributes.
241             #
242             #“use bytes” is not necessary because we know the first character is
243             #0x31, which came from Convert::ASN1.
244 9         2730 substr($attr_enc, 0, 1) = chr 0xa0;
245              
246 9         62 my %reqinfo = (
247             version => 0,
248             subject => $subj_enc,
249             subjectPKInfo => $pk_der,
250             attributes => $attr_enc,
251             );
252              
253 9         49 my $reqinfo_enc = $asn1_reqinfo->encode(\%reqinfo);
254              
255 9         1588 my $signature = $sig_func->( $key, $reqinfo_enc );
256              
257             return {
258 9   33     365295 certificationRequestInfo => \%reqinfo,
259             signatureAlgorithm => {
260             algorithm => $sig_alg,
261             parameters => $sig_param || Crypt::Perl::ASN1::NULL(),
262             },
263             signature => $signature,
264             };
265             }
266              
267             1;