Skip to content

Commit b65acfd

Browse files
authored
Merge pull request #5594 from LibreSign/backport/5587/stable31
[stable31] feat: implement serial number with random number
2 parents 936789f + 66b941c commit b65acfd

File tree

8 files changed

+87
-4
lines changed

8 files changed

+87
-4
lines changed

lib/Handler/CertificateEngine/OpenSslHandler.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ public function generateRootCert(
4747

4848
$csr = openssl_csr_new($this->getCsrNames(), $privateKey, ['digest_alg' => 'sha256']);
4949
$options = $this->getRootCertOptions();
50-
$x509 = openssl_csr_sign($csr, null, $privateKey, $days = 365 * 5, $options);
50+
51+
$serialNumber = random_int(1000000, 2147483647);
52+
53+
$x509 = openssl_csr_sign($csr, null, $privateKey, $days = 365 * 5, $options, $serialNumber);
5154

5255
openssl_csr_export($csr, $csrout);
5356
openssl_x509_export($x509, $certout);
@@ -92,12 +95,13 @@ public function generateCertificate(): string {
9295
throw new LibresignException('OpenSSL error: ' . $message);
9396
}
9497

98+
$serialNumber = random_int(1000000, 2147483647);
99+
95100
$x509 = openssl_csr_sign($csr, $rootCertificate, $rootPrivateKey, $this->expirity(), [
96101
'config' => $this->getFilenameToLeafCert(),
97-
// This will set "basicConstraints" to CA:FALSE, the default is CA:TRUE
98-
// The signer certificate is not a Certificate Authority
99102
'x509_extensions' => 'v3_req',
100-
]);
103+
], $serialNumber);
104+
101105
return parent::exportToPkcs12(
102106
$x509,
103107
$privateKey,

lib/ResponseDefinitions.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@
6868
* subject: string,
6969
* issuer: string,
7070
* extensions: string,
71+
* serialNumber: string,
72+
* serialNumberHex: string,
7173
* validate: array{
7274
* from: string,
7375
* to: string,

openapi-full.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@
106106
"subject",
107107
"issuer",
108108
"extensions",
109+
"serialNumber",
110+
"serialNumberHex",
109111
"validate"
110112
],
111113
"properties": {
@@ -121,6 +123,12 @@
121123
"extensions": {
122124
"type": "string"
123125
},
126+
"serialNumber": {
127+
"type": "string"
128+
},
129+
"serialNumberHex": {
130+
"type": "string"
131+
},
124132
"validate": {
125133
"type": "object",
126134
"required": [

openapi.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@
106106
"subject",
107107
"issuer",
108108
"extensions",
109+
"serialNumber",
110+
"serialNumberHex",
109111
"validate"
110112
],
111113
"properties": {
@@ -121,6 +123,12 @@
121123
"extensions": {
122124
"type": "string"
123125
},
126+
"serialNumber": {
127+
"type": "string"
128+
},
129+
"serialNumberHex": {
130+
"type": "string"
131+
},
124132
"validate": {
125133
"type": "object",
126134
"required": [

src/types/openapi/openapi-full.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,6 +1293,8 @@ export type components = {
12931293
subject: string;
12941294
issuer: string;
12951295
extensions: string;
1296+
serialNumber: string;
1297+
serialNumberHex: string;
12961298
validate: {
12971299
from: string;
12981300
to: string;

src/types/openapi/openapi.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,8 @@ export type components = {
980980
subject: string;
981981
issuer: string;
982982
extensions: string;
983+
serialNumber: string;
984+
serialNumberHex: string;
983985
validate: {
984986
from: string;
985987
to: string;

src/views/ReadCertificate/CertificateContent.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@
6767
<span class="field-label">{{ t('libresign', 'Serial number') }}</span>
6868
<span class="field-value">{{ certificate.serialNumber }}</span>
6969
</div>
70+
<div v-if="certificate.serialNumberHex" class="certificate-field">
71+
<span class="field-label">{{ t('libresign', 'Serial number (hex)') }}</span>
72+
<span class="field-value">{{ certificate.serialNumberHex }}</span>
73+
</div>
7074
</div>
7175
</NcSettingsSection>
7276

tests/php/Unit/Handler/CertificateEngine/OpenSslHandlerTest.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,57 @@ public static function dataReadCertificate(): array {
245245
],
246246
];
247247
}
248+
249+
public function testSerialNumberGeneration(): void {
250+
$rootInstance = $this->getInstance();
251+
$rootInstance->generateRootCert('', []);
252+
253+
$signerInstance = $this->getInstance();
254+
$signerInstance->setCommonName('Test User');
255+
$signerInstance->setPassword('123456');
256+
257+
$certificate = $signerInstance->generateCertificate();
258+
$parsed = $signerInstance->readCertificate($certificate, '123456');
259+
260+
$this->assertArrayHasKey('serialNumber', $parsed, 'Certificate should have serialNumber field');
261+
$this->assertArrayHasKey('serialNumberHex', $parsed, 'Certificate should have serialNumberHex field');
262+
$this->assertNotNull($parsed['serialNumber'], 'Serial number should not be null');
263+
$this->assertNotNull($parsed['serialNumberHex'], 'Serial number hex should not be null');
264+
265+
$this->assertNotEquals('0', $parsed['serialNumber'], 'Serial number should not be zero');
266+
$this->assertNotEquals('00', $parsed['serialNumberHex'], 'Serial number hex should not be zero');
267+
268+
$serialInt = (int)$parsed['serialNumber'];
269+
$this->assertGreaterThanOrEqual(1000000, $serialInt, 'Serial number should be >= 1000000');
270+
$this->assertLessThanOrEqual(2147483647, $serialInt, 'Serial number should be <= 2147483647');
271+
272+
$this->assertIsNumeric($parsed['serialNumber'], 'Serial number should be numeric');
273+
$this->assertMatchesRegularExpression('/^[0-9A-Fa-f]+$/', $parsed['serialNumberHex'], 'Serial number hex should contain only hex characters');
274+
}
275+
276+
public function testUniqueSerialNumbers(): void {
277+
$rootInstance = $this->getInstance();
278+
$rootInstance->generateRootCert('', []);
279+
280+
$serialNumbers = [];
281+
$numCertificates = 3;
282+
283+
for ($i = 0; $i < $numCertificates; $i++) {
284+
$signerInstance = $this->getInstance();
285+
$signerInstance->setCommonName("Test Certificate $i");
286+
$signerInstance->setPassword('123456');
287+
$certificateContent = $signerInstance->generateCertificate();
288+
$parsed = $signerInstance->readCertificate($certificateContent, '123456');
289+
290+
$serialNumber = $parsed['serialNumber'];
291+
292+
$this->assertNotEquals('0', $serialNumber, "Certificate $i should not have serial number 0");
293+
294+
$this->assertNotContains($serialNumber, $serialNumbers, "Certificate $i should have unique serial number");
295+
296+
$serialNumbers[] = $serialNumber;
297+
}
298+
299+
$this->assertCount($numCertificates, array_unique($serialNumbers), 'All serial numbers should be unique');
300+
}
248301
}

0 commit comments

Comments
 (0)