Skip to content

Commit 2e4ec1a

Browse files
authored
Merge pull request #5731 from LibreSign/feat/add-multiple-ou
feat: add multiple ou
2 parents b17fffe + 4eab882 commit 2e4ec1a

27 files changed

+807
-120
lines changed

lib/Command/Configure/Cfssl.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ protected function configure(): void {
2828
->addOption(
2929
name: 'ou',
3030
shortcut: null,
31-
mode: InputOption::VALUE_REQUIRED,
32-
description: 'Organization unit'
31+
mode: InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
32+
description: 'Organization unit (can be specified multiple times)'
3333
)
3434
->addOption(
3535
name: 'o',

lib/Command/Configure/OpenSsl.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ protected function configure(): void {
2828
->addOption(
2929
name: 'ou',
3030
shortcut: null,
31-
mode: InputOption::VALUE_REQUIRED,
32-
description: 'Organization unit'
31+
mode: InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
32+
description: 'Organization unit (can be specified multiple times)'
3333
)
3434
->addOption(
3535
name: 'o',

lib/Controller/AdminController.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public function __construct(
6969
/**
7070
* Generate certificate using CFSSL engine
7171
*
72-
* @param array{commonName: string, names: array<string, array{value:string}>} $rootCert fields of root certificate
72+
* @param array{commonName: string, names: array<string, array{value:string|array<string>}>} $rootCert fields of root certificate
7373
* @param string $cfsslUri URI of CFSSL API
7474
* @param string $configPath Path of config files of CFSSL
7575
* @return DataResponse<Http::STATUS_OK, array{data: LibresignEngineHandler}, array{}>|DataResponse<Http::STATUS_UNAUTHORIZED, array{message: string}, array{}>
@@ -106,7 +106,7 @@ public function generateCertificateCfssl(
106106
/**
107107
* Generate certificate using OpenSSL engine
108108
*
109-
* @param array{commonName: string, names: array<string, array{value:string}>} $rootCert fields of root certificate
109+
* @param array{commonName: string, names: array<string, array{value:string|array<string>}>} $rootCert fields of root certificate
110110
* @param string $configPath Path of config files of CFSSL
111111
* @return DataResponse<Http::STATUS_OK, array{data: LibresignEngineHandler}, array{}>|DataResponse<Http::STATUS_UNAUTHORIZED, array{message: string}, array{}>
112112
*
@@ -145,7 +145,12 @@ private function generateCertificate(
145145
if (isset($rootCert['names'])) {
146146
$this->validateService->validateNames($rootCert['names']);
147147
foreach ($rootCert['names'] as $item) {
148-
$names[$item['id']]['value'] = trim((string)$item['value']);
148+
if (is_array($item['value'])) {
149+
$trimmedValues = array_map('trim', $item['value']);
150+
$names[$item['id']]['value'] = array_filter($trimmedValues, fn ($val) => $val !== '');
151+
} else {
152+
$names[$item['id']]['value'] = trim((string)$item['value']);
153+
}
149154
}
150155
}
151156
$this->validateService->validate('CN', $rootCert['commonName']);

lib/Handler/CertificateEngine/AEngineHandler.php

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@
4444
* @method string getLocality()
4545
* @method IEngineHandler setOrganization(string $organization)
4646
* @method string getOrganization()
47-
* @method IEngineHandler setOrganizationalUnit(string $organizationalUnit)
48-
* @method string getOrganizationalUnit()
47+
* @method IEngineHandler setOrganizationalUnit(array $organizationalUnit)
48+
* @method array getOrganizationalUnit()
4949
* @method IEngineHandler setUID(string $UID)
5050
* @method string getName()
5151
*/
@@ -60,7 +60,7 @@ abstract class AEngineHandler implements IEngineHandler {
6060
protected string $state = '';
6161
protected string $locality = '';
6262
protected string $organization = '';
63-
protected string $organizationalUnit = '';
63+
protected array $organizationalUnit = [];
6464
protected string $UID = '';
6565
protected string $password = '';
6666
protected string $configPath = '';
@@ -140,6 +140,16 @@ private function parseX509(string $x509): array {
140140

141141
$return = self::convertArrayToUtf8($parsed);
142142

143+
foreach (['subject', 'issuer'] as $actor) {
144+
foreach ($return[$actor] as $part => $value) {
145+
if (is_string($value) && str_contains($value, ', ')) {
146+
$return[$actor][$part] = explode(', ', $value);
147+
} else {
148+
$return[$actor][$part] = $value;
149+
}
150+
}
151+
}
152+
143153
$return['valid_from'] = $this->dateTimeFormatter->formatDateTime($parsed['validFrom_time_t']);
144154
$return['valid_to'] = $this->dateTimeFormatter->formatDateTime($parsed['validTo_time_t']);
145155
return $return;
@@ -464,6 +474,11 @@ protected function checkRootCertificateModernFeatures(): ?ConfigureCheckHelper {
464474
$minorIssues[] = "Missing modern extensions: {$extensionsList}";
465475
}
466476

477+
$hasLibresignCaUuid = $this->validateLibresignCaUuidInCertificate($parsed);
478+
if (!$hasLibresignCaUuid) {
479+
$minorIssues[] = 'LibreSign CA UUID not found in Organizational Unit';
480+
}
481+
467482
if (!empty($criticalIssues)) {
468483
$issuesList = implode(', ', $criticalIssues);
469484
return (new ConfigureCheckHelper())
@@ -490,6 +505,45 @@ protected function checkRootCertificateModernFeatures(): ?ConfigureCheckHelper {
490505
}
491506
}
492507

508+
private function validateLibresignCaUuidInCertificate(array $parsed): bool {
509+
if (!isset($parsed['subject']['OU'])) {
510+
return false;
511+
}
512+
513+
$instanceId = $this->getInstanceId();
514+
if (empty($instanceId)) {
515+
return false;
516+
}
517+
518+
$organizationalUnits = $parsed['subject']['OU'];
519+
520+
if (is_string($organizationalUnits)) {
521+
if (str_contains($organizationalUnits, ', ')) {
522+
$organizationalUnits = explode(', ', $organizationalUnits);
523+
} else {
524+
$organizationalUnits = [$organizationalUnits];
525+
}
526+
}
527+
528+
$expectedCaUuid = 'libresign-ca-id:' . $instanceId;
529+
530+
foreach ($organizationalUnits as $ou) {
531+
if (trim($ou) === $expectedCaUuid) {
532+
return true;
533+
}
534+
}
535+
536+
return false;
537+
}
538+
539+
private function getInstanceId(): string {
540+
$instanceId = $this->appConfig->getValueString(Application::APP_ID, 'instance_id', '');
541+
if (strlen($instanceId) === 10) {
542+
return $instanceId;
543+
}
544+
return '';
545+
}
546+
493547
#[\Override]
494548
public function toArray(): array {
495549
$return = [

lib/Handler/CertificateEngine/CfsslHandler.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@ private function newCert(): array {
177177
];
178178

179179
$names = $this->getNames();
180+
foreach ($names as $key => $value) {
181+
if (!empty($value) && is_array($value)) {
182+
$names[$key] = implode(', ', $value);
183+
}
184+
}
180185
if (!empty($names)) {
181186
$json['json']['request']['names'][] = $names;
182187
}

lib/Handler/CertificateEngine/IEngineHandler.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
* @method string getLocality()
2626
* @method IEngineHandler setOrganization(string $organization)
2727
* @method string getOrganization()
28-
* @method IEngineHandler setOrganizationalUnit(string $organizationalUnit)
29-
* @method string getOrganizationalUnit()
28+
* @method IEngineHandler setOrganizationalUnit(array $organizationalUnit)
29+
* @method array getOrganizationalUnit()
3030
* @method IEngineHandler setUID(string $UID)
3131
* @method string getUID()
3232
* @method string getName()

lib/Handler/CertificateEngine/OpenSslHandler.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,9 +316,17 @@ private function getCsrNames(): array {
316316
$distinguishedNames['UID'] = $value;
317317
continue;
318318
}
319+
319320
$longName = $this->translateToLong($name);
320321
$longName = lcfirst($longName) . 'Name';
321-
$distinguishedNames[$longName] = $value;
322+
323+
if (is_array($value)) {
324+
if (!empty($value)) {
325+
$distinguishedNames[$longName] = implode(', ', $value);
326+
}
327+
} else {
328+
$distinguishedNames[$longName] = $value;
329+
}
322330
}
323331
if ($this->getCommonName()) {
324332
$distinguishedNames['commonName'] = $this->getCommonName();
@@ -493,6 +501,4 @@ private function createCrlConfig(array $revokedCertificates): string {
493501

494502
return $configFile;
495503
}
496-
497-
498504
}

lib/Handler/CfsslServerHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ private function putCsrServer(
8383
$content['crl_url'] = $crlUrl;
8484
}
8585
foreach ($names as $id => $name) {
86-
$content['names'][0][$id] = $name['value'];
86+
$content['names'][0][$id] = is_array($name['value']) ? implode(', ', $name['value']) : $name['value'];
8787
}
8888
($this->getConfigPath)();
8989
$response = file_put_contents($this->csrServerFile, json_encode($content));

lib/Handler/SignEngine/Pkcs12Handler.php

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ private function extractCertificateChain(string $signature): array {
168168
'label' => $this->l10n->t('Signature is valid.'),
169169
];
170170
if (!$isLibreSignRootCA) {
171-
$isLibreSignRootCA = $this->isLibreSignRootCA($pemCertificate);
171+
$isLibreSignRootCA = $this->isLibreSignRootCA($pemCertificate, $parsed);
172172
}
173173
$parsed['isLibreSignRootCA'] = $isLibreSignRootCA;
174174
$chain[$index] = $parsed;
@@ -183,14 +183,35 @@ private function extractCertificateChain(string $signature): array {
183183
return $chain;
184184
}
185185

186-
private function isLibreSignRootCA(string $certificate): bool {
186+
private function isLibreSignRootCA(string $certificate, array $parsed): bool {
187187
$rootCertificatePem = $this->getRootCertificatePem();
188188
if (empty($rootCertificatePem)) {
189189
return false;
190190
}
191+
191192
$rootFingerprint = openssl_x509_fingerprint($rootCertificatePem, 'sha256');
192193
$fingerprint = openssl_x509_fingerprint($certificate, 'sha256');
193-
return $rootFingerprint === $fingerprint;
194+
if ($rootFingerprint === $fingerprint) {
195+
return true;
196+
}
197+
198+
return $this->hasLibreSignCaId($parsed);
199+
}
200+
201+
private function hasLibreSignCaId(array $parsed): bool {
202+
$instanceId = $this->appConfig->getValueString(Application::APP_ID, 'instance_id', '');
203+
if (strlen($instanceId) !== 10 || !isset($parsed['subject']['OU'])) {
204+
return false;
205+
}
206+
207+
$expectedCaUuid = 'libresign-ca-id:' . $instanceId;
208+
$organizationalUnits = $parsed['subject']['OU'];
209+
210+
if (is_string($organizationalUnits)) {
211+
return str_contains($organizationalUnits, $expectedCaUuid);
212+
}
213+
214+
return in_array($expectedCaUuid, array_map('trim', $organizationalUnits));
194215
}
195216

196217
private function getRootCertificatePem(): string {

lib/Migration/Version13000Date20251031165700.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
4646
}
4747
}
4848

49+
$this->convertRootCertOuStringToArray();
50+
4951
if ($schema->hasTable('libresign_crl')) {
5052
$crlTable = $schema->getTable('libresign_crl');
5153
if (!$crlTable->hasColumn('engine')) {
@@ -54,4 +56,18 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
5456
}
5557
return $schema;
5658
}
59+
60+
private function convertRootCertOuStringToArray(): void {
61+
$rootCert = $this->appConfig->getValueArray(Application::APP_ID, 'rootCert');
62+
if (!$rootCert || !isset($rootCert['names']['OU']['value'])) {
63+
return;
64+
}
65+
66+
$ouValue = $rootCert['names']['OU']['value'];
67+
68+
if (is_string($ouValue)) {
69+
$rootCert['names']['OU']['value'] = [$ouValue];
70+
$this->appConfig->setValueArray(Application::APP_ID, 'rootCert', $rootCert);
71+
}
72+
}
5773
}

0 commit comments

Comments
 (0)