GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

TBSCertificate   C
last analyzed

Complexity

Total Complexity 56

Size/Duplication

Total Lines 619
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 56
eloc 164
dl 0
loc 619
ccs 184
cts 184
cp 1
rs 5.5199
c 0
b 0
f 0

34 Methods

Rating   Name   Duplication   Size   Complexity  
A withVersion() 0 5 1
A version() 0 6 2
A withIssuerUniqueID() 0 5 1
A __construct() 0 8 1
A sign() 0 15 3
A fromCSR() 0 17 3
A hasSubjectUniqueID() 0 3 1
A withIssuerCertificate() 0 10 1
A withSignature() 0 5 1
A withSubjectPublicKeyInfo() 0 5 1
A subject() 0 3 1
A withExtensions() 0 5 1
A subjectUniqueID() 0 6 2
A withRandomSerialNumber() 0 9 2
A withAdditionalExtensions() 0 5 1
A withIssuer() 0 5 1
A extensions() 0 3 1
A withSerialNumber() 0 5 1
A withSubjectUniqueID() 0 5 1
B fromASN1() 0 38 6
A serialNumber() 0 6 2
A hasIssuerUniqueID() 0 3 1
A withSubject() 0 5 1
A issuer() 0 3 1
A signature() 0 6 2
A hasSerialNumber() 0 3 1
A withValidity() 0 5 1
A hasSignature() 0 3 1
A hasVersion() 0 3 1
A issuerUniqueID() 0 6 2
A _determineVersion() 0 11 4
A validity() 0 3 1
A toASN1() 0 27 5
A subjectPublicKeyInfo() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like TBSCertificate often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TBSCertificate, and based on these observations, apply Extract Interface, too.

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