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.
Test Failed
Push — master ( 405cf3...79c9ba )
by Joni
04:48
created

TBSCertificate::withSignature()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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