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.
Completed
Push — master ( 439333...eb5c8e )
by Joni
05:28
created

TBSCertificate   F

Complexity

Total Complexity 55

Size/Duplication

Total Lines 563
Duplicated Lines 1.6 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 55
c 0
b 0
f 0
lcom 1
cbo 17
dl 9
loc 563
ccs 200
cts 200
cp 1
rs 3.5483

34 Methods

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

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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
namespace X509\Certificate;
4
5
use ASN1\Element;
6
use ASN1\Type\Constructed\Sequence;
7
use ASN1\Type\Primitive\Integer;
8
use ASN1\Type\Tagged\ExplicitlyTaggedType;
9
use ASN1\Type\Tagged\ImplicitlyTaggedType;
10
use CryptoUtil\ASN1\AlgorithmIdentifier;
11
use CryptoUtil\ASN1\AlgorithmIdentifier\Feature\AlgorithmIdentifierType;
12
use CryptoUtil\ASN1\AlgorithmIdentifier\Feature\SignatureAlgorithmIdentifier;
13
use CryptoUtil\ASN1\PrivateKeyInfo;
14
use CryptoUtil\ASN1\PublicKeyInfo;
15
use CryptoUtil\Crypto\Crypto;
16
use X501\ASN1\Name;
17
use X509\Certificate\Extension\AuthorityKeyIdentifierExtension;
18
use X509\Certificate\Extension\Extension;
19
use X509\Certificate\Extension\SubjectKeyIdentifierExtension;
20
use X509\CertificationRequest\CertificationRequest;
21
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
39
	 */
40
	protected $_version;
41
	
42
	/**
43
	 * Serial number.
44
	 *
45
	 * @var int
46
	 */
47
	protected $_serialNumber;
48
	
49
	/**
50
	 * Signature algorithm.
51
	 *
52
	 * @var AlgorithmIdentifierType
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 18
	public function __construct(Name $subject, PublicKeyInfo $pki, Name $issuer, 
114
			Validity $validity) {
115 18
		$this->_subject = $subject;
116 18
		$this->_subjectPublicKeyInfo = $pki;
117 18
		$this->_issuer = $issuer;
118 18
		$this->_validity = $validity;
119 18
		$this->_extensions = new Extensions();
120 18
	}
121
	
122
	/**
123
	 * Initialize from ASN.1.
124
	 *
125
	 * @param Sequence $seq
126
	 * @return self
127
	 */
128 12
	public static function fromASN1(Sequence $seq) {
129 12
		$idx = 0;
130 12
		if ($seq->hasTagged(0)) {
131 9
			$idx++;
132 9
			$version = intval(
133 9
				$seq->getTagged(0)
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ASN1\Element as the method explicit() does only exist in the following sub-classes of ASN1\Element: ASN1\Type\Tagged\DERTaggedType, ASN1\Type\Tagged\ExplicitlyTaggedType. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
134 9
					->explicit(Element::TYPE_INTEGER)
135 9
					->number());
136 9
		} else {
137 3
			$version = self::VERSION_1;
138
		}
139 12
		$serial = $seq->at($idx++, Element::TYPE_INTEGER)->number();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ASN1\Element as the method number() does only exist in the following sub-classes of ASN1\Element: ASN1\Type\Primitive\Enumerated, ASN1\Type\Primitive\Integer. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
140 12
		$algo = AlgorithmIdentifier::fromASN1(
141 12
			$seq->at($idx++, Element::TYPE_SEQUENCE));
0 ignored issues
show
Compatibility introduced by
$seq->at($idx++, \ASN1\Element::TYPE_SEQUENCE) of type object<ASN1\Element> is not a sub-type of object<ASN1\Type\Constructed\Sequence>. It seems like you assume a child class of the class ASN1\Element to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
142 12
		$issuer = Name::fromASN1($seq->at($idx++, Element::TYPE_SEQUENCE));
0 ignored issues
show
Compatibility introduced by
$seq->at($idx++, \ASN1\Element::TYPE_SEQUENCE) of type object<ASN1\Element> is not a sub-type of object<ASN1\Type\Constructed\Sequence>. It seems like you assume a child class of the class ASN1\Element to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
143 12
		$validity = Validity::fromASN1($seq->at($idx++, Element::TYPE_SEQUENCE));
0 ignored issues
show
Compatibility introduced by
$seq->at($idx++, \ASN1\Element::TYPE_SEQUENCE) of type object<ASN1\Element> is not a sub-type of object<ASN1\Type\Constructed\Sequence>. It seems like you assume a child class of the class ASN1\Element to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
144 12
		$subject = Name::fromASN1($seq->at($idx++, Element::TYPE_SEQUENCE));
0 ignored issues
show
Compatibility introduced by
$seq->at($idx++, \ASN1\Element::TYPE_SEQUENCE) of type object<ASN1\Element> is not a sub-type of object<ASN1\Type\Constructed\Sequence>. It seems like you assume a child class of the class ASN1\Element to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
145 12
		$pki = PublicKeyInfo::fromASN1($seq->at($idx++, Element::TYPE_SEQUENCE));
0 ignored issues
show
Compatibility introduced by
$seq->at($idx++, \ASN1\Element::TYPE_SEQUENCE) of type object<ASN1\Element> is not a sub-type of object<ASN1\Type\Constructed\Sequence>. It seems like you assume a child class of the class ASN1\Element to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
146 12
		$tbs_cert = new self($subject, $pki, $issuer, $validity);
147 12
		$tbs_cert->_version = $version;
148 12
		$tbs_cert->_serialNumber = $serial;
149 12
		$tbs_cert->_signature = $algo;
150 12
		if ($seq->hasTagged(1)) {
151 1
			$tbs_cert->_issuerUniqueID = UniqueIdentifier::fromASN1(
152 1
				$seq->getTagged(1)->implicit(Element::TYPE_BIT_STRING));
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ASN1\Element as the method implicit() does only exist in the following sub-classes of ASN1\Element: ASN1\Type\Tagged\DERTaggedType, ASN1\Type\Tagged\ImplicitlyTaggedType. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
153 1
		}
154 12
		if ($seq->hasTagged(2)) {
155 1
			$tbs_cert->_subjectUniqueID = UniqueIdentifier::fromASN1(
156 1
				$seq->getTagged(2)->implicit(Element::TYPE_BIT_STRING));
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ASN1\Element as the method implicit() does only exist in the following sub-classes of ASN1\Element: ASN1\Type\Tagged\DERTaggedType, ASN1\Type\Tagged\ImplicitlyTaggedType. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
157 1
		}
158 12
		if ($seq->hasTagged(3)) {
159 9
			$tbs_cert->_extensions = Extensions::fromASN1(
160 9
				$seq->getTagged(3)->explicit(Element::TYPE_SEQUENCE));
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ASN1\Element as the method explicit() does only exist in the following sub-classes of ASN1\Element: ASN1\Type\Tagged\DERTaggedType, ASN1\Type\Tagged\ExplicitlyTaggedType. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
161 9
		}
162 12
		return $tbs_cert;
163
	}
164
	
165
	/**
166
	 * Initialize from certification request.
167
	 *
168
	 * Note that signature is not verified and must be done by the caller.
169
	 *
170
	 * @param CertificationRequest $cr
171
	 * @return self
172
	 */
173 1
	public static function fromCSR(CertificationRequest $cr) {
174 1
		$cri = $cr->certificationRequestInfo();
175 1
		$tbs_cert = new self($cri->subject(), $cri->subjectPKInfo(), new Name(), 
176 1
			Validity::fromStrings(null, null));
177
		// if CSR has Extension Request attribute
178 1
		if ($cri->hasAttributes()) {
179 1
			$attribs = $cri->attributes();
180 1
			if ($attribs->hasExtensionRequest()) {
181 1
				$tbs_cert = $tbs_cert->withExtensions(
182 1
					$attribs->extensionRequest()
183 1
						->extensions());
184 1
			}
185 1
		}
186
		// add Subject Key Identifier extension
187 1
		$tbs_cert = $tbs_cert->withAdditionalExtensions(
188 1
			new SubjectKeyIdentifierExtension(false, 
189 1
				$cri->subjectPKInfo()
190 1
					->keyIdentifier()));
191 1
		return $tbs_cert;
192
	}
193
	
194
	/**
195
	 * Get self with fields set from the issuer's certificate.
196
	 *
197
	 * Issuer shall be set to issuing certificate's subject.
198
	 * Authority key identifier extensions shall be added with a key identifier
199
	 * set to issuing certificate's public key identifier.
200
	 *
201
	 * @param Certificate $cert Issuing party's certificate
202
	 * @return self
203
	 */
204 1
	public function withIssuerCertificate(Certificate $cert) {
205 1
		$obj = clone $this;
206
		// set issuer DN from cert's subject
207 1
		$obj->_issuer = $cert->tbsCertificate()->subject();
208
		// add authority key identifier extension
209 1
		$key_id = $cert->tbsCertificate()
210 1
			->subjectPublicKeyInfo()
211 1
			->keyIdentifier();
212 1
		$obj->_extensions = $obj->_extensions->withExtensions(
213 1
			new AuthorityKeyIdentifierExtension(false, $key_id));
214 1
		return $obj;
215
	}
216
	
217
	/**
218
	 * Get self with given version.
219
	 *
220
	 * If version is not set, appropriate version is automatically
221
	 * determined during signing.
222
	 *
223
	 * @param int $version
224
	 * @return self
225
	 */
226 4
	public function withVersion($version) {
227 4
		$obj = clone $this;
228 4
		$obj->_version = $version;
229 4
		return $obj;
230
	}
231
	
232
	/**
233
	 * Get self with given serial number.
234
	 *
235
	 * @param int|string $serial Base 10 number
236
	 * @return self
237
	 */
238 5
	public function withSerialNumber($serial) {
239 5
		$obj = clone $this;
240 5
		$obj->_serialNumber = $serial;
0 ignored issues
show
Documentation Bug introduced by
It seems like $serial can also be of type string. However, the property $_serialNumber is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
241 5
		return $obj;
242
	}
243
	
244
	/**
245
	 * Get self with random positive serial number.
246
	 *
247
	 * @param int $size Number of random bytes
248
	 * @return self
249
	 */
250 1 View Code Duplication
	public function withRandomSerialNumber($size = 16) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
251
		// ensure that first byte is always non-zero and having first bit unset
252 1
		$num = gmp_init(mt_rand(1, 0x7f), 10);
253 1
		for ($i = 1; $i < $size; ++$i) {
254 1
			$num <<= 8;
255 1
			$num += mt_rand(0, 0xff);
256 1
		}
257 1
		return $this->withSerialNumber(gmp_strval($num, 10));
258
	}
259
	
260
	/**
261
	 * Get self with given signature algorithm.
262
	 *
263
	 * @param SignatureAlgorithmIdentifier $algo
264
	 * @return self
265
	 */
266 4
	public function withSignature(SignatureAlgorithmIdentifier $algo) {
267 4
		$obj = clone $this;
268 4
		$obj->_signature = $algo;
269 4
		return $obj;
270
	}
271
	
272
	/**
273
	 * Get self with given issuer.
274
	 *
275
	 * @param Name $issuer
276
	 * @return self
277
	 */
278 1
	public function withIssuer(Name $issuer) {
279 1
		$obj = clone $this;
280 1
		$obj->_issuer = $issuer;
281 1
		return $obj;
282
	}
283
	
284
	/**
285
	 * Get self with given validity.
286
	 *
287
	 * @param Validity $validity
288
	 * @return self
289
	 */
290 2
	public function withValidity(Validity $validity) {
291 2
		$obj = clone $this;
292 2
		$obj->_validity = $validity;
293 2
		return $obj;
294
	}
295
	
296
	/**
297
	 * Get self with given subject.
298
	 *
299
	 * @param Name $subject
300
	 * @return self
301
	 */
302 1
	public function withSubject(Name $subject) {
303 1
		$obj = clone $this;
304 1
		$obj->_subject = $subject;
305 1
		return $obj;
306
	}
307
	
308
	/**
309
	 * Get self with given subject public key info.
310
	 *
311
	 * @param PublicKeyInfo $pub_key_info
312
	 * @return self
313
	 */
314 1
	public function withSubjectPublicKeyInfo(PublicKeyInfo $pub_key_info) {
315 1
		$obj = clone $this;
316 1
		$obj->_subjectPublicKeyInfo = $pub_key_info;
317 1
		return $obj;
318
	}
319
	
320
	/**
321
	 * Get self with issuer unique ID.
322
	 *
323
	 * @param UniqueIdentifier $id
324
	 * @return self
325
	 */
326 4
	public function withIssuerUniqueID(UniqueIdentifier $id) {
327 4
		$obj = clone $this;
328 4
		$obj->_issuerUniqueID = $id;
329 4
		return $obj;
330
	}
331
	
332
	/**
333
	 * Get self with subject unique ID.
334
	 *
335
	 * @param UniqueIdentifier $id
336
	 * @return self
337
	 */
338 4
	public function withSubjectUniqueID(UniqueIdentifier $id) {
339 4
		$obj = clone $this;
340 4
		$obj->_subjectUniqueID = $id;
341 4
		return $obj;
342
	}
343
	
344
	/**
345
	 * Get self with given extensions.
346
	 *
347
	 * @param Extensions $extensions
348
	 * @return self
349
	 */
350 4
	public function withExtensions(Extensions $extensions) {
351 4
		$obj = clone $this;
352 4
		$obj->_extensions = $extensions;
353 4
		return $obj;
354
	}
355
	
356
	/**
357
	 * Get self with extensions added.
358
	 *
359
	 * @param Extension ...$exts One or more Extension objects
360
	 * @return self
361
	 */
362 3
	public function withAdditionalExtensions(Extension ...$exts) {
363 3
		$obj = clone $this;
364 3
		$obj->_extensions = $obj->_extensions->withExtensions(...$exts);
365 3
		return $obj;
366
	}
367
	
368
	/**
369
	 * Check whether version is set.
370
	 *
371
	 * @return bool
372
	 */
373 41
	public function hasVersion() {
374 41
		return isset($this->_version);
375
	}
376
	
377
	/**
378
	 * Get certificate version.
379
	 *
380
	 * @return int
381
	 */
382 41
	public function version() {
383 41
		if (!$this->hasVersion()) {
384 1
			throw new \LogicException("version not set.");
385
		}
386 40
		return $this->_version;
387
	}
388
	
389
	/**
390
	 * Check whether serial number is set.
391
	 *
392
	 * @return bool
393
	 */
394 44
	public function hasSerialNumber() {
395 44
		return isset($this->_serialNumber);
396
	}
397
	
398
	/**
399
	 * Get serial number.
400
	 *
401
	 * @return int|string Base 10 integer
402
	 */
403 44
	public function serialNumber() {
404 44
		if (!$this->hasSerialNumber()) {
405 1
			throw new \LogicException("serialNumber not set.");
406
		}
407 43
		return $this->_serialNumber;
408
	}
409
	
410
	/**
411
	 * Check whether signature algorithm is set.
412
	 *
413
	 * @return bool
414
	 */
415 41
	public function hasSignature() {
416 41
		return isset($this->_signature);
417
	}
418
	
419
	/**
420
	 * Get signature algorithm.
421
	 *
422
	 * @return AlgorithmIdentifierType
423
	 */
424 41
	public function signature() {
425 41
		if (!$this->hasSignature()) {
426 1
			throw new \LogicException("signature not set.");
427
		}
428 40
		return $this->_signature;
429
	}
430
	
431
	/**
432
	 * Get issuer.
433
	 *
434
	 * @return Name
435
	 */
436 37
	public function issuer() {
437 37
		return $this->_issuer;
438
	}
439
	
440
	/**
441
	 * Get validity period.
442
	 *
443
	 * @return Validity
444
	 */
445 25
	public function validity() {
446 25
		return $this->_validity;
447
	}
448
	
449
	/**
450
	 * Get subject.
451
	 *
452
	 * @return Name
453
	 */
454 36
	public function subject() {
455 36
		return $this->_subject;
456
	}
457
	
458
	/**
459
	 * Get subject public key.
460
	 *
461
	 * @return PublicKeyInfo
462
	 */
463 31
	public function subjectPublicKeyInfo() {
464 31
		return $this->_subjectPublicKeyInfo;
465
	}
466
	
467
	/**
468
	 * Whether issuer unique identifier is present.
469
	 *
470
	 * @return bool
471
	 */
472 3
	public function hasIssuerUniqueID() {
473 3
		return isset($this->_issuerUniqueID);
474
	}
475
	
476
	/**
477
	 * Get issuerUniqueID.
478
	 *
479
	 * @return UniqueIdentifier
480
	 */
481 2
	public function issuerUniqueID() {
482 2
		if (!$this->hasIssuerUniqueID()) {
483 1
			throw new \LogicException("issuerUniqueID not set.");
484
		}
485 1
		return $this->_issuerUniqueID;
486
	}
487
	
488
	/**
489
	 * Whether subject unique identifier is present.
490
	 *
491
	 * @return bool
492
	 */
493 2
	public function hasSubjectUniqueID() {
494 2
		return isset($this->_subjectUniqueID);
495
	}
496
	
497
	/**
498
	 * Get subjectUniqueID.
499
	 *
500
	 * @return UniqueIdentifier
501
	 */
502 2
	public function subjectUniqueID() {
503 2
		if (!$this->hasSubjectUniqueID()) {
504 1
			throw new \LogicException("subjectUniqueID not set.");
505
		}
506 1
		return $this->_subjectUniqueID;
507
	}
508
	
509
	/**
510
	 * Get extensions.
511
	 *
512
	 * @return Extensions
513
	 */
514 35
	public function extensions() {
515 35
		return $this->_extensions;
516
	}
517
	
518
	/**
519
	 * Generate ASN.1 structure.
520
	 *
521
	 * @return Sequence
522
	 */
523 38
	public function toASN1() {
524 38
		$elements = array();
525 38
		$version = $this->version();
526
		// if version is not default
527 38
		if ($version != self::VERSION_1) {
528 29
			$elements[] = new ExplicitlyTaggedType(0, new Integer($version));
529 29
		}
530 38
		$serial = $this->serialNumber();
531 38
		$signature = $this->signature();
532
		// add required elements
533 38
		array_push($elements, new Integer($serial), $signature->toASN1(), 
534 38
			$this->_issuer->toASN1(), $this->_validity->toASN1(), 
535 38
			$this->_subject->toASN1(), $this->_subjectPublicKeyInfo->toASN1());
536 38
		if (isset($this->_issuerUniqueID)) {
537 3
			$elements[] = new ImplicitlyTaggedType(1, 
538 3
				$this->_issuerUniqueID->toASN1());
539 3
		}
540 38
		if (isset($this->_subjectUniqueID)) {
541 3
			$elements[] = new ImplicitlyTaggedType(2, 
542 3
				$this->_subjectUniqueID->toASN1());
543 3
		}
544 38
		if (count($this->_extensions)) {
545 25
			$elements[] = new ExplicitlyTaggedType(3, 
546 25
				$this->_extensions->toASN1());
547 25
		}
548 38
		return new Sequence(...$elements);
549
	}
550
	
551
	/**
552
	 * Create signed certificate.
553
	 *
554
	 * @param Crypto $crypto Crypto engine
555
	 * @param SignatureAlgorithmIdentifier $algo Algorithm used for signing
556
	 * @param PrivateKeyInfo $privkey_info Private key used for signing
557
	 * @return Certificate
558
	 */
559 9
	public function sign(Crypto $crypto, SignatureAlgorithmIdentifier $algo, 
560
			PrivateKeyInfo $privkey_info) {
561 9
		$tbsCert = clone $this;
562 9
		if (!isset($tbsCert->_version)) {
563 9
			$tbsCert->_version = $tbsCert->_determineVersion();
564 9
		}
565 9
		if (!isset($tbsCert->_serialNumber)) {
566 9
			$tbsCert->_serialNumber = 0;
567 9
		}
568 9
		$tbsCert->_signature = $algo;
569 9
		$data = $tbsCert->toASN1()->toDER();
570 9
		$signature = $crypto->sign($data, $privkey_info, $algo);
571 9
		return new Certificate($tbsCert, $algo, $signature);
572
	}
573
	
574
	/**
575
	 * Determine minimum version for the certificate.
576
	 *
577
	 * @return int
578
	 */
579 9
	protected function _determineVersion() {
580
		// if extensions are present
581 9
		if (count($this->_extensions)) {
582 3
			return self::VERSION_3;
583
		}
584
		// if UniqueIdentifier is present
585 6
		if (isset($this->_issuerUniqueID) || isset($this->_subjectUniqueID)) {
586 3
			return self::VERSION_2;
587
		}
588 3
		return self::VERSION_1;
589
	}
590
}
591