TBSCertificate::_determineVersion()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 6
cts 6
cp 1
rs 9.8666
c 0
b 0
f 0
nc 3
cc 4
nop 0
crap 4
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace X509\Certificate;
6
7
use ASN1\Element;
8
use ASN1\Type\Constructed\Sequence;
9
use ASN1\Type\Primitive\Integer;
10
use ASN1\Type\Tagged\ExplicitlyTaggedType;
11
use ASN1\Type\Tagged\ImplicitlyTaggedType;
12
use Sop\CryptoBridge\Crypto;
13
use Sop\CryptoTypes\AlgorithmIdentifier\AlgorithmIdentifier;
14
use Sop\CryptoTypes\AlgorithmIdentifier\Feature\SignatureAlgorithmIdentifier;
15
use Sop\CryptoTypes\Asymmetric\PrivateKeyInfo;
16
use Sop\CryptoTypes\Asymmetric\PublicKeyInfo;
17
use X501\ASN1\Name;
18
use X509\Certificate\Extension\AuthorityKeyIdentifierExtension;
19
use X509\Certificate\Extension\Extension;
20
use X509\Certificate\Extension\SubjectKeyIdentifierExtension;
21
use X509\CertificationRequest\CertificationRequest;
22
23
/**
24
 * Implements <i>TBSCertificate</i> ASN.1 type.
25
 *
26
 * @link https://tools.ietf.org/html/rfc5280#section-4.1.2
27
 */
28
class TBSCertificate
29
{
30
    // Certificate version enumerations
31
    const VERSION_1 = 0;
32
    const VERSION_2 = 1;
33
    const VERSION_3 = 2;
34
    
35
    /**
36
     * Certificate version.
37
     *
38
     * @var int|null
39
     */
40
    protected $_version;
41
    
42
    /**
43
     * Serial number.
44
     *
45
     * @var string|null
46
     */
47
    protected $_serialNumber;
48
    
49
    /**
50
     * Signature algorithm.
51
     *
52
     * @var SignatureAlgorithmIdentifier|null
53
     */
54
    protected $_signature;
55
    
56
    /**
57
     * Certificate issuer.
58
     *
59
     * @var Name $_issuer
60
     */
61
    protected $_issuer;
62
    
63
    /**
64
     * Certificate validity period.
65
     *
66
     * @var Validity $_validity
67
     */
68
    protected $_validity;
69
    
70
    /**
71
     * Certificate subject.
72
     *
73
     * @var Name $_subject
74
     */
75
    protected $_subject;
76
    
77
    /**
78
     * Subject public key.
79
     *
80
     * @var PublicKeyInfo $_subjectPublicKeyInfo
81
     */
82
    protected $_subjectPublicKeyInfo;
83
    
84
    /**
85
     * Issuer unique identifier.
86
     *
87
     * @var UniqueIdentifier|null $_issuerUniqueID
88
     */
89
    protected $_issuerUniqueID;
90
    
91
    /**
92
     * Subject unique identifier.
93
     *
94
     * @var UniqueIdentifier|null $_subjectUniqueID
95
     */
96
    protected $_subjectUniqueID;
97
    
98
    /**
99
     * Extensions.
100
     *
101
     * @var Extensions $_extensions
102
     */
103
    protected $_extensions;
104
    
105
    /**
106
     * Constructor.
107
     *
108
     * @param Name $subject Certificate subject
109
     * @param PublicKeyInfo $pki Subject public key
110
     * @param Name $issuer Certificate issuer
111
     * @param Validity $validity Validity period
112
     */
113 29
    public function __construct(Name $subject, PublicKeyInfo $pki, Name $issuer,
114
        Validity $validity)
115
    {
116 29
        $this->_subject = $subject;
117 29
        $this->_subjectPublicKeyInfo = $pki;
118 29
        $this->_issuer = $issuer;
119 29
        $this->_validity = $validity;
120 29
        $this->_extensions = new Extensions();
121 29
    }
122
    
123
    /**
124
     * Initialize from ASN.1.
125
     *
126
     * @param Sequence $seq
127
     * @return self
128
     */
129 21
    public static function fromASN1(Sequence $seq): self
130
    {
131 21
        $idx = 0;
132 21
        if ($seq->hasTagged(0)) {
133 17
            $idx++;
134 17
            $version = $seq->getTagged(0)
135 17
                ->asExplicit()
136 17
                ->asInteger()
137 17
                ->intNumber();
138
        } else {
139 4
            $version = self::VERSION_1;
140
        }
141 21
        $serial = $seq->at($idx++)
142 21
            ->asInteger()
143 21
            ->number();
144 21
        $algo = AlgorithmIdentifier::fromASN1($seq->at($idx++)->asSequence());
145 21
        if (!$algo instanceof SignatureAlgorithmIdentifier) {
146 1
            throw new \UnexpectedValueException(
147 1
                "Unsupported signature algorithm " . $algo->name() . ".");
148
        }
149 20
        $issuer = Name::fromASN1($seq->at($idx++)->asSequence());
150 20
        $validity = Validity::fromASN1($seq->at($idx++)->asSequence());
151 20
        $subject = Name::fromASN1($seq->at($idx++)->asSequence());
152 20
        $pki = PublicKeyInfo::fromASN1($seq->at($idx++)->asSequence());
153 20
        $tbs_cert = new self($subject, $pki, $issuer, $validity);
154 20
        $tbs_cert->_version = $version;
155 20
        $tbs_cert->_serialNumber = $serial;
156 20
        $tbs_cert->_signature = $algo;
157 20
        if ($seq->hasTagged(1)) {
158 1
            $tbs_cert->_issuerUniqueID = UniqueIdentifier::fromASN1(
159 1
                $seq->getTagged(1)
160 1
                    ->asImplicit(Element::TYPE_BIT_STRING)
161 1
                    ->asBitString());
162
        }
163 20
        if ($seq->hasTagged(2)) {
164 1
            $tbs_cert->_subjectUniqueID = UniqueIdentifier::fromASN1(
165 1
                $seq->getTagged(2)
166 1
                    ->asImplicit(Element::TYPE_BIT_STRING)
167 1
                    ->asBitString());
168
        }
169 20
        if ($seq->hasTagged(3)) {
170 16
            $tbs_cert->_extensions = Extensions::fromASN1(
171 16
                $seq->getTagged(3)
172 16
                    ->asExplicit()
173 16
                    ->asSequence());
174
        }
175 20
        return $tbs_cert;
176
    }
177
    
178
    /**
179
     * Initialize from certification request.
180
     *
181
     * Note that signature is not verified and must be done by the caller.
182
     *
183
     * @param CertificationRequest $cr
184
     * @return self
185
     */
186 1
    public static function fromCSR(CertificationRequest $cr): self
187
    {
188 1
        $cri = $cr->certificationRequestInfo();
189 1
        $tbs_cert = new self($cri->subject(), $cri->subjectPKInfo(), new Name(),
190 1
            Validity::fromStrings(null, null));
191
192
        // if CSR has Extension Request attribute
193 1
        if ($cri->hasAttributes()) {
194 1
            $attribs = $cri->attributes();
195 1
            if ($attribs->hasExtensionRequest()) {
196 1
                $tbs_cert = $tbs_cert->withExtensions(
197 1
                    $attribs->extensionRequest()
198 1
                        ->extensions());
199
            }
200
        }
201
        
202 1
        return $tbs_cert;
203
    }
204
    
205
    /**
206
     * Get self with fields set from the issuer's certificate.
207
     *
208
     * Issuer shall be set to issuing certificate's subject.
209
     * Authority key identifier extensions shall be added with a key identifier
210
     * set to issuing certificate's public key identifier.
211
     *
212
     * @param Certificate $cert Issuing party's certificate
213
     * @return self
214
     */
215 1
    public function withIssuerCertificate(Certificate $cert): self
216
    {
217 1
        $obj = clone $this;
218
        // set issuer DN from cert's subject
219 1
        $obj->_issuer = $cert->tbsCertificate()->subject();
220
        // add authority key identifier extension
221 1
        $key_id = $cert->tbsCertificate()
222 1
            ->subjectPublicKeyInfo()
223 1
            ->keyIdentifier();
224 1
        $obj->_extensions = $obj->_extensions->withExtensions(
225 1
            new AuthorityKeyIdentifierExtension(false, $key_id));
226 1
        return $obj;
227
    }
228
    
229
    /**
230
     * Get self with given version.
231
     *
232
     * If version is not set, appropriate version is automatically
233
     * determined during signing.
234
     *
235
     * @param int $version
236
     * @return self
237
     */
238 4
    public function withVersion(int $version): self
239
    {
240 4
        $obj = clone $this;
241 4
        $obj->_version = $version;
242 4
        return $obj;
243
    }
244
    
245
    /**
246
     * Get self with given serial number.
247
     *
248
     * @param int|string $serial Base 10 number
249
     * @return self
250
     */
251 5
    public function withSerialNumber($serial): self
252
    {
253 5
        $obj = clone $this;
254 5
        $obj->_serialNumber = strval($serial);
255 5
        return $obj;
256
    }
257
    
258
    /**
259
     * Get self with random positive serial number.
260
     *
261
     * @param int $size Number of random bytes
262
     * @return self
263
     */
264 1
    public function withRandomSerialNumber(int $size = 16): self
265
    {
266
        // ensure that first byte is always non-zero and having first bit unset
267 1
        $num = gmp_init(mt_rand(1, 0x7f), 10);
268 1
        for ($i = 1; $i < $size; ++$i) {
269 1
            $num <<= 8;
270 1
            $num += mt_rand(0, 0xff);
271
        }
272 1
        return $this->withSerialNumber(gmp_strval($num, 10));
273
    }
274
    
275
    /**
276
     * Get self with given signature algorithm.
277
     *
278
     * @param SignatureAlgorithmIdentifier $algo
279
     * @return self
280
     */
281 4
    public function withSignature(SignatureAlgorithmIdentifier $algo): self
282
    {
283 4
        $obj = clone $this;
284 4
        $obj->_signature = $algo;
285 4
        return $obj;
286
    }
287
    
288
    /**
289
     * Get self with given issuer.
290
     *
291
     * @param Name $issuer
292
     * @return self
293
     */
294 1
    public function withIssuer(Name $issuer): self
295
    {
296 1
        $obj = clone $this;
297 1
        $obj->_issuer = $issuer;
298 1
        return $obj;
299
    }
300
    
301
    /**
302
     * Get self with given validity.
303
     *
304
     * @param Validity $validity
305
     * @return self
306
     */
307 2
    public function withValidity(Validity $validity): self
308
    {
309 2
        $obj = clone $this;
310 2
        $obj->_validity = $validity;
311 2
        return $obj;
312
    }
313
    
314
    /**
315
     * Get self with given subject.
316
     *
317
     * @param Name $subject
318
     * @return self
319
     */
320 2
    public function withSubject(Name $subject): self
321
    {
322 2
        $obj = clone $this;
323 2
        $obj->_subject = $subject;
324 2
        return $obj;
325
    }
326
    
327
    /**
328
     * Get self with given subject public key info.
329
     *
330
     * @param PublicKeyInfo $pub_key_info
331
     * @return self
332
     */
333 1
    public function withSubjectPublicKeyInfo(PublicKeyInfo $pub_key_info): self
334
    {
335 1
        $obj = clone $this;
336 1
        $obj->_subjectPublicKeyInfo = $pub_key_info;
337 1
        return $obj;
338
    }
339
    
340
    /**
341
     * Get self with issuer unique ID.
342
     *
343
     * @param UniqueIdentifier $id
344
     * @return self
345
     */
346 6
    public function withIssuerUniqueID(UniqueIdentifier $id): self
347
    {
348 6
        $obj = clone $this;
349 6
        $obj->_issuerUniqueID = $id;
350 6
        return $obj;
351
    }
352
    
353
    /**
354
     * Get self with subject unique ID.
355
     *
356
     * @param UniqueIdentifier $id
357
     * @return self
358
     */
359 4
    public function withSubjectUniqueID(UniqueIdentifier $id): self
360
    {
361 4
        $obj = clone $this;
362 4
        $obj->_subjectUniqueID = $id;
363 4
        return $obj;
364
    }
365
    
366
    /**
367
     * Get self with given extensions.
368
     *
369
     * @param Extensions $extensions
370
     * @return self
371
     */
372 4
    public function withExtensions(Extensions $extensions): self
373
    {
374 4
        $obj = clone $this;
375 4
        $obj->_extensions = $extensions;
376 4
        return $obj;
377
    }
378
    
379
    /**
380
     * Get self with extensions added.
381
     *
382
     * @param Extension ...$exts One or more Extension objects
383
     * @return self
384
     */
385 3
    public function withAdditionalExtensions(Extension ...$exts): self
386
    {
387 3
        $obj = clone $this;
388 3
        $obj->_extensions = $obj->_extensions->withExtensions(...$exts);
389 3
        return $obj;
390
    }
391
    
392
    /**
393
     * Check whether version is set.
394
     *
395
     * @return bool
396
     */
397 68
    public function hasVersion(): bool
398
    {
399 68
        return isset($this->_version);
400
    }
401
    
402
    /**
403
     * Get certificate version.
404
     *
405
     * @return int
406
     */
407 68
    public function version(): int
408
    {
409 68
        if (!$this->hasVersion()) {
410 1
            throw new \LogicException("version not set.");
411
        }
412 67
        return $this->_version;
413
    }
414
    
415
    /**
416
     * Check whether serial number is set.
417
     *
418
     * @return bool
419
     */
420 91
    public function hasSerialNumber(): bool
421
    {
422 91
        return isset($this->_serialNumber);
423
    }
424
    
425
    /**
426
     * Get serial number.
427
     *
428
     * @return string Base 10 integer
429
     */
430 91
    public function serialNumber(): string
431
    {
432 91
        if (!$this->hasSerialNumber()) {
433 1
            throw new \LogicException("serialNumber not set.");
434
        }
435 90
        return $this->_serialNumber;
436
    }
437
    
438
    /**
439
     * Check whether signature algorithm is set.
440
     *
441
     * @return bool
442
     */
443 68
    public function hasSignature(): bool
444
    {
445 68
        return isset($this->_signature);
446
    }
447
    
448
    /**
449
     * Get signature algorithm.
450
     *
451
     * @return SignatureAlgorithmIdentifier
452
     */
453 68
    public function signature(): SignatureAlgorithmIdentifier
454
    {
455 68
        if (!$this->hasSignature()) {
456 1
            throw new \LogicException("signature not set.");
457
        }
458 67
        return $this->_signature;
459
    }
460
    
461
    /**
462
     * Get issuer.
463
     *
464
     * @return Name
465
     */
466 68
    public function issuer(): Name
467
    {
468 68
        return $this->_issuer;
469
    }
470
    
471
    /**
472
     * Get validity period.
473
     *
474
     * @return Validity
475
     */
476 46
    public function validity(): Validity
477
    {
478 46
        return $this->_validity;
479
    }
480
    
481
    /**
482
     * Get subject.
483
     *
484
     * @return Name
485
     */
486 68
    public function subject(): Name
487
    {
488 68
        return $this->_subject;
489
    }
490
    
491
    /**
492
     * Get subject public key.
493
     *
494
     * @return PublicKeyInfo
495
     */
496 68
    public function subjectPublicKeyInfo(): PublicKeyInfo
497
    {
498 68
        return $this->_subjectPublicKeyInfo;
499
    }
500
    
501
    /**
502
     * Whether issuer unique identifier is present.
503
     *
504
     * @return bool
505
     */
506 7
    public function hasIssuerUniqueID(): bool
507
    {
508 7
        return isset($this->_issuerUniqueID);
509
    }
510
    
511
    /**
512
     * Get issuerUniqueID.
513
     *
514
     * @return UniqueIdentifier
515
     */
516 4
    public function issuerUniqueID(): UniqueIdentifier
517
    {
518 4
        if (!$this->hasIssuerUniqueID()) {
519 1
            throw new \LogicException("issuerUniqueID not set.");
520
        }
521 3
        return $this->_issuerUniqueID;
522
    }
523
    
524
    /**
525
     * Whether subject unique identifier is present.
526
     *
527
     * @return bool
528
     */
529 2
    public function hasSubjectUniqueID(): bool
530
    {
531 2
        return isset($this->_subjectUniqueID);
532
    }
533
    
534
    /**
535
     * Get subjectUniqueID.
536
     *
537
     * @return UniqueIdentifier
538
     */
539 2
    public function subjectUniqueID(): UniqueIdentifier
540
    {
541 2
        if (!$this->hasSubjectUniqueID()) {
542 1
            throw new \LogicException("subjectUniqueID not set.");
543
        }
544 1
        return $this->_subjectUniqueID;
545
    }
546
    
547
    /**
548
     * Get extensions.
549
     *
550
     * @return Extensions
551
     */
552 62
    public function extensions(): Extensions
553
    {
554 62
        return $this->_extensions;
555
    }
556
    
557
    /**
558
     * Generate ASN.1 structure.
559
     *
560
     * @return Sequence
561
     */
562 65
    public function toASN1(): Sequence
563
    {
564 65
        $elements = array();
565 65
        $version = $this->version();
566
        // if version is not default
567 65
        if ($version != self::VERSION_1) {
568 54
            $elements[] = new ExplicitlyTaggedType(0, new Integer($version));
569
        }
570 65
        $serial = $this->serialNumber();
571 65
        $signature = $this->signature();
572
        // add required elements
573 65
        array_push($elements, new Integer($serial), $signature->toASN1(),
574 65
            $this->_issuer->toASN1(), $this->_validity->toASN1(),
575 65
            $this->_subject->toASN1(), $this->_subjectPublicKeyInfo->toASN1());
576 65
        if (isset($this->_issuerUniqueID)) {
577 6
            $elements[] = new ImplicitlyTaggedType(1,
578 6
                $this->_issuerUniqueID->toASN1());
579
        }
580 65
        if (isset($this->_subjectUniqueID)) {
581 4
            $elements[] = new ImplicitlyTaggedType(2,
582 4
                $this->_subjectUniqueID->toASN1());
583
        }
584 65
        if (count($this->_extensions)) {
585 48
            $elements[] = new ExplicitlyTaggedType(3,
586 48
                $this->_extensions->toASN1());
587
        }
588 65
        return new Sequence(...$elements);
589
    }
590
    
591
    /**
592
     * Create signed certificate.
593
     *
594
     * @param SignatureAlgorithmIdentifier $algo Algorithm used for signing
595
     * @param PrivateKeyInfo $privkey_info Private key used for signing
596
     * @param Crypto|null $crypto Crypto engine, use default if not set
597
     * @return Certificate
598
     */
599 12
    public function sign(SignatureAlgorithmIdentifier $algo,
600
        PrivateKeyInfo $privkey_info, Crypto $crypto = null): Certificate
601
    {
602 12
        $crypto = $crypto ?: Crypto::getDefault();
603 12
        $tbs_cert = clone $this;
604 12
        if (!isset($tbs_cert->_version)) {
605 12
            $tbs_cert->_version = $tbs_cert->_determineVersion();
606
        }
607 12
        if (!isset($tbs_cert->_serialNumber)) {
608 12
            $tbs_cert->_serialNumber = strval(0);
609
        }
610 12
        $tbs_cert->_signature = $algo;
611 12
        $data = $tbs_cert->toASN1()->toDER();
612 12
        $signature = $crypto->sign($data, $privkey_info, $algo);
613 12
        return new Certificate($tbs_cert, $algo, $signature);
614
    }
615
    
616
    /**
617
     * Determine minimum version for the certificate.
618
     *
619
     * @return int
620
     */
621 12
    protected function _determineVersion(): int
622
    {
623
        // if extensions are present
624 12
        if (count($this->_extensions)) {
625 3
            return self::VERSION_3;
626
        }
627
        // if UniqueIdentifier is present
628 9
        if (isset($this->_issuerUniqueID) || isset($this->_subjectUniqueID)) {
629 5
            return self::VERSION_2;
630
        }
631 4
        return self::VERSION_1;
632
    }
633
}
634