Skip to content

Commit 5e23d8d

Browse files
committed
osbuild: new stage 'cacert'
1 parent 0584c20 commit 5e23d8d

10 files changed

Lines changed: 273 additions & 1 deletion

File tree

‎pkg/blueprint/ca_customizations.go‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package blueprint
2+
3+
type CACustomization struct {
4+
PEMCerts []string `json:"pem_certs,omitempty" toml:"pem_certs,omitempty"`
5+
}

‎pkg/blueprint/customizations.go‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"slices"
77
"strings"
88

9+
"github.com/osbuild/images/pkg/cert"
910
"github.com/osbuild/images/pkg/customizations/anaconda"
1011
)
1112

@@ -31,6 +32,7 @@ type Customizations struct {
3132
Installer *InstallerCustomization `json:"installer,omitempty" toml:"installer,omitempty"`
3233
RPM *RPMCustomization `json:"rpm,omitempty" toml:"rpm,omitempty"`
3334
RHSM *RHSMCustomization `json:"rhsm,omitempty" toml:"rhsm,omitempty"`
35+
CACerts *CACustomization `json:"cacerts,omitempty" toml:"ca,omitempty"`
3436
}
3537

3638
type IgnitionCustomization struct {
@@ -425,3 +427,32 @@ func (c *Customizations) GetRHSM() *RHSMCustomization {
425427
}
426428
return c.RHSM
427429
}
430+
431+
func (c *Customizations) checkCACerts() error {
432+
if c == nil {
433+
return nil
434+
}
435+
436+
if c.CACerts != nil {
437+
for _, bundle := range c.CACerts.PEMCerts {
438+
_, err := cert.ParseCerts(bundle)
439+
if err != nil {
440+
return err
441+
}
442+
}
443+
}
444+
445+
return nil
446+
}
447+
448+
func (c *Customizations) GetCACerts() (*CACustomization, error) {
449+
if c == nil {
450+
return nil, nil
451+
}
452+
453+
if err := c.checkCACerts(); err != nil {
454+
return nil, err
455+
}
456+
457+
return c.CACerts, nil
458+
}

‎pkg/cert/parsecerts.go‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package cert
2+
3+
import (
4+
"crypto/x509"
5+
"encoding/pem"
6+
"errors"
7+
"fmt"
8+
)
9+
10+
var ErrNoValidPEMCertificatesFound = errors.New("no valid PEM certificates found")
11+
12+
// ParseCerts parses a PEM-encoded certificate chain formatted as concatenated strings
13+
// and returns a slice of x509.Certificate. In case of unparsable certificates, the
14+
// function returns an empty slice.
15+
func ParseCerts(cert string) ([]*x509.Certificate, error) {
16+
result := make([]*x509.Certificate, 0, 1)
17+
block := []byte(cert)
18+
var blocks [][]byte
19+
for {
20+
var certDERBlock *pem.Block
21+
certDERBlock, block = pem.Decode(block)
22+
if certDERBlock == nil {
23+
break
24+
}
25+
26+
if certDERBlock.Type == "CERTIFICATE" {
27+
blocks = append(blocks, certDERBlock.Bytes)
28+
}
29+
}
30+
31+
for _, block := range blocks {
32+
cert, err := x509.ParseCertificate(block)
33+
if err != nil {
34+
return nil, fmt.Errorf("failed to parse certificate: %w", err)
35+
}
36+
result = append(result, cert)
37+
}
38+
39+
if len(result) == 0 {
40+
return nil, fmt.Errorf("%w in: %s", ErrNoValidPEMCertificatesFound, cert)
41+
}
42+
43+
return result, nil
44+
}

‎pkg/cert/parsecerts_test.go‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package cert
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
// taken from osbuild:test/data/certs/cert1.pem
10+
const exampleCert = `
11+
-----BEGIN CERTIFICATE-----
12+
MIIDhTCCAm2gAwIBAgIUVya7VJ3O8W8SqwuEa0BZ4HSsXvAwDQYJKoZIhvcNAQEL
13+
BQAwUTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVy
14+
bGluMQwwCgYDVQQKDANPcmcxEjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0yNDA4MjYx
15+
MDQyNDBaGA8yMTI0MDgwMjEwNDI0MFowUTELMAkGA1UEBhMCREUxDzANBgNVBAgM
16+
BkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMQwwCgYDVQQKDANPcmcxEjAQBgNVBAMM
17+
CWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnGjlvN
18+
O3F/Z7Lr/r+6Xp2DosnNwoPHhG2e61KnFzgZfaxbklal5ORpuV/gLIg7lrbpdZe7
19+
WvK+16RanL6fLitis/tYVFyvz1MXqBYYrEoFGvVg9fOiis7hjpdZcpNDH9SngoAN
20+
O0Wvv4T6LQS0cC7ZAFZjvmJ+RiZEbzRkNG5pUddZXbotE6htNfLgA5L1wIBgllrM
21+
4DVkG0yNKmzqPNzfPTbdUgWCfjaQShHy1GP8KNEwFxM31F2wvQxsEb77o1S44Out
22+
mlsi83tti6P7KjDk7w2j2zZO1X0xI8pflv3TBkJT1Am8vnk6rVnNO4pCpop3+kma
23+
pDUEzBQmSQA5R1ECAwEAAaNTMFEwHQYDVR0OBBYEFDxFcFgPEsgsDixfKxB0uYGN
24+
aJmzMB8GA1UdIwQYMBaAFDxFcFgPEsgsDixfKxB0uYGNaJmzMA8GA1UdEwEB/wQF
25+
MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFih4lUbLlhKwIAV9x3/W7Mih8xUEdZr
26+
olquZgaHedFet+ByAHvoES3pec7AVYTOD53mjgyZubD6INnVHzKyS4AG9ydD73o4
27+
cmm3DKxBaesvlHeTn0MOKsoM8QCxeyFJmiUPpgDBok/PFnbGR9+JcsrlGJAnsSKD
28+
vWpiwYcBauZ9nnK5yDe5M9XNFPkNDZzbKvWU7Sw3ziMT/+bRJse5vTrYcyOnNGgy
29+
gZNz2nimKy1U8XZVAVwOV0rdGEFrfMln8DkRW86rGK/EncaVsl0SSP/rmjQgiX8Q
30+
3CZraQGujJP932HSwUfdCX9yh+rTjE3MEnbqMoLzJa4BXB2aDQWtywU=
31+
-----END CERTIFICATE-----
32+
`
33+
34+
func TestParseCerts(t *testing.T) {
35+
certs, err := ParseCerts(exampleCert)
36+
assert.Nil(t, err)
37+
assert.Equal(t, len(certs), 1)
38+
assert.Equal(t, "localhost", certs[0].Subject.CommonName)
39+
}
40+
41+
func TestParseConcatenatedCerts(t *testing.T) {
42+
certs, err := ParseCerts(exampleCert + "\n" + exampleCert)
43+
assert.Nil(t, err)
44+
assert.Equal(t, len(certs), 2)
45+
assert.Equal(t, "localhost", certs[0].Subject.CommonName)
46+
}
47+
48+
func TestParseGarbageCerts(t *testing.T) {
49+
_, err := ParseCerts("garbage")
50+
assert.NotNil(t, err)
51+
assert.Equal(t, "no valid PEM certificates found in: garbage", err.Error())
52+
}

‎pkg/distro/fedora/images.go‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,14 @@ func osCustomizations(
226226
osc.Files = append(osc.Files, imageConfig.Files...)
227227
osc.Directories = append(osc.Directories, imageConfig.Directories...)
228228

229+
ca, err := c.GetCACerts()
230+
if err != nil {
231+
panic(fmt.Sprintf("unexpected error checking CA certs: %v", err))
232+
}
233+
if ca != nil {
234+
osc.CACerts = ca.PEMCerts
235+
}
236+
229237
return osc, nil
230238
}
231239

‎pkg/distro/rhel/images.go‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,14 @@ func osCustomizations(
271271
osc.NoBLS = *imageConfig.NoBLS
272272
}
273273

274+
ca, err := c.GetCACerts()
275+
if err != nil {
276+
panic(fmt.Sprintf("unexpected error checking CA certs: %v", err))
277+
}
278+
if ca != nil {
279+
osc.CACerts = ca.PEMCerts
280+
}
281+
274282
return osc, nil
275283
}
276284

‎pkg/manifest/os.go‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ type OSCustomizations struct {
141141
Directories []*fsnode.Directory
142142
Files []*fsnode.File
143143

144+
CACerts []string
145+
144146
FIPS bool
145147

146148
// NoBLS configures the image bootloader with traditional menu entries
@@ -791,6 +793,21 @@ func (p *OS) serialize() osbuild.Pipeline {
791793
}))
792794
}
793795

796+
if len(p.CACerts) > 0 {
797+
for _, cc := range p.CACerts {
798+
files, err := osbuild.NewCAFileNodes(cc)
799+
if err != nil {
800+
panic(err.Error())
801+
}
802+
803+
if len(files) > 0 {
804+
p.Files = append(p.Files, files...)
805+
pipeline.AddStages(osbuild.GenFileNodesStages(files)...)
806+
}
807+
}
808+
pipeline.AddStage(osbuild.NewCAStageStage())
809+
}
810+
794811
if p.SElinux != "" {
795812
pipeline.AddStage(osbuild.NewSELinuxStage(&osbuild.SELinuxStageOptions{
796813
FileContexts: fmt.Sprintf("etc/selinux/%s/contexts/files/file_contexts", p.SElinux),
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package osbuild
2+
3+
import (
4+
"encoding/pem"
5+
"fmt"
6+
"path/filepath"
7+
8+
"github.com/osbuild/images/pkg/cert"
9+
"github.com/osbuild/images/pkg/customizations/fsnode"
10+
)
11+
12+
func NewCAStageStage() *Stage {
13+
return &Stage{
14+
Type: "org.osbuild.pki.update-ca-trust",
15+
}
16+
}
17+
18+
func NewCAFileNodes(bundle string) ([]*fsnode.File, error) {
19+
var files []*fsnode.File
20+
certs, err := cert.ParseCerts(bundle)
21+
if err != nil {
22+
return nil, fmt.Errorf("failed to parse CA certificates: %v", err)
23+
}
24+
25+
for _, c := range certs {
26+
path := filepath.Join("/etc/pki/ca-trust/source/anchors", filepath.Base(c.SerialNumber.Text(16))+".pem")
27+
f, err := fsnode.NewFile(path, nil, "root", "root", pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: c.Raw}))
28+
if err != nil {
29+
panic(err)
30+
}
31+
files = append(files, f)
32+
}
33+
34+
return files, nil
35+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package osbuild
2+
3+
import (
4+
"encoding/pem"
5+
"testing"
6+
7+
"github.com/osbuild/images/pkg/cert"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
// taken from osbuild:test/data/certs/cert{1,2}.pem
12+
const exampleCert = `
13+
-----BEGIN CERTIFICATE-----
14+
MIIDhTCCAm2gAwIBAgIUVya7VJ3O8W8SqwuEa0BZ4HSsXvAwDQYJKoZIhvcNAQEL
15+
BQAwUTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVy
16+
bGluMQwwCgYDVQQKDANPcmcxEjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0yNDA4MjYx
17+
MDQyNDBaGA8yMTI0MDgwMjEwNDI0MFowUTELMAkGA1UEBhMCREUxDzANBgNVBAgM
18+
BkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMQwwCgYDVQQKDANPcmcxEjAQBgNVBAMM
19+
CWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnGjlvN
20+
O3F/Z7Lr/r+6Xp2DosnNwoPHhG2e61KnFzgZfaxbklal5ORpuV/gLIg7lrbpdZe7
21+
WvK+16RanL6fLitis/tYVFyvz1MXqBYYrEoFGvVg9fOiis7hjpdZcpNDH9SngoAN
22+
O0Wvv4T6LQS0cC7ZAFZjvmJ+RiZEbzRkNG5pUddZXbotE6htNfLgA5L1wIBgllrM
23+
4DVkG0yNKmzqPNzfPTbdUgWCfjaQShHy1GP8KNEwFxM31F2wvQxsEb77o1S44Out
24+
mlsi83tti6P7KjDk7w2j2zZO1X0xI8pflv3TBkJT1Am8vnk6rVnNO4pCpop3+kma
25+
pDUEzBQmSQA5R1ECAwEAAaNTMFEwHQYDVR0OBBYEFDxFcFgPEsgsDixfKxB0uYGN
26+
aJmzMB8GA1UdIwQYMBaAFDxFcFgPEsgsDixfKxB0uYGNaJmzMA8GA1UdEwEB/wQF
27+
MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFih4lUbLlhKwIAV9x3/W7Mih8xUEdZr
28+
olquZgaHedFet+ByAHvoES3pec7AVYTOD53mjgyZubD6INnVHzKyS4AG9ydD73o4
29+
cmm3DKxBaesvlHeTn0MOKsoM8QCxeyFJmiUPpgDBok/PFnbGR9+JcsrlGJAnsSKD
30+
vWpiwYcBauZ9nnK5yDe5M9XNFPkNDZzbKvWU7Sw3ziMT/+bRJse5vTrYcyOnNGgy
31+
gZNz2nimKy1U8XZVAVwOV0rdGEFrfMln8DkRW86rGK/EncaVsl0SSP/rmjQgiX8Q
32+
3CZraQGujJP932HSwUfdCX9yh+rTjE3MEnbqMoLzJa4BXB2aDQWtywU=
33+
-----END CERTIFICATE-----
34+
-----BEGIN CERTIFICATE-----
35+
MIIDhTCCAm2gAwIBAgIUBFUPUz3MqjWa0bINLdvSfNDQ3iQwDQYJKoZIhvcNAQEL
36+
BQAwUTELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVy
37+
bGluMQwwCgYDVQQKDANPcmcxEjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0yNDA4MjYx
38+
MDQyNDVaGA8yMTI0MDgwMjEwNDI0NVowUTELMAkGA1UEBhMCREUxDzANBgNVBAgM
39+
BkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMQwwCgYDVQQKDANPcmcxEjAQBgNVBAMM
40+
CWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN/SEvmx
41+
m9UHhP/rXQrx82SmIft940HoJtM9Bsbp1Pj8Nk7WA+M/WgTVz0J/08uU8pZgzj0U
42+
pfaawxfB2lo++GBa9vCeqQzIo6YKQf0Rg50+KJ1oQ3+ZIDxIr2ou38dg5KyG1D+A
43+
XnGIg7fKu3RczCYbgudB2Yq2LvQdLTSb+PE08lczc7e9bGobHLfqCilFS71Q2BEa
44+
B14xfrz3LF1Cf0r6qL6SXg2+vyjsMy1sGwGY1XGqAgvT8rgm+ZfVknFLyzhly6Cf
45+
eVNg+JWapR5iDVIG6F64Ayj63wd52aveYD3JEljpzF3I8lq4qQ8oI3z0S/t4Dx01
46+
ErexkyAFhIlWVvsCAwEAAaNTMFEwHQYDVR0OBBYEFDccfxp07zFNvuWqvSTgFmju
47+
JrfCMB8GA1UdIwQYMBaAFDccfxp07zFNvuWqvSTgFmjuJrfCMA8GA1UdEwEB/wQF
48+
MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAK2sJWg9Eg2Ekv7TgQeln7lGWNl2EYS5
49+
uDc1qhgkOos3QSonvSU9n90DECY+m3wcHBWF6OSB1RFSQmROdi3tKMZUgM9gnjG/
50+
Us8D8HxS0+FLYX0wHlx/anCBzy+tKMDKq+tQzuZZGpKFhDZf527Uqj+R8DVMcqKV
51+
oqdILvb3asYiISCXDu2Fj9HfqPHKKER6hboQ2LvcLq+t4FOtvGiTxdiDaOhSJWcL
52+
0Izcs3Wi0RUNTIULtLFukFvYCTFoxYSLP178xXSf3N8Z9o6ezb7aUI86d6rLmwga
53+
GHY+cBQvEsjW85elwClvspptKd1Xx4z8geH0+Y0qaIGBn1cyze5k5fk=
54+
-----END CERTIFICATE-----
55+
`
56+
57+
func TestNewCAFileNodes(t *testing.T) {
58+
certs, err := cert.ParseCerts(exampleCert)
59+
assert.Nil(t, err)
60+
61+
files, err := NewCAFileNodes(exampleCert)
62+
assert.Nil(t, err)
63+
assert.Equal(t, 2, len(certs), len(files))
64+
for i, c := range certs {
65+
assert.Equal(t, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: c.Raw}), files[i].Data())
66+
}
67+
}

‎test/configs/all-customizations.json‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,12 @@
171171
"-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQGiBGRBSJURBACzCoe9UNfxOUiFLq9b60weSBFdr39mLViscecDWATNvXtgRoK/\nxl/4qpayzALRCQ2Ek/pMrbKPF/3ngECuBv7S+rI4n/rIia4FNcqzYeZAz4DE4NP/\neUGvz49tWhmH17hX/rmF9kz5kLq2bDZI4GDgZW/oMDdt2ivj092Ljm9jRwCgyQy3\nWEK6RJvIcSEh9vbdwVdMPOcD/iHqNejTMFwGyZfCWB0eIOoxUOUn/ZZpELTL2UpW\nGduCf3txb5SkK7M+WDbb0S5IvNXoi0tc13STiD6Oxg2O9PkSvvYb+8zxlhNoSTwy\n54j7Rf5FlnQ3TAFfjtQ5LCx56LKK73j4RjvKW//ktm5n54exsgo9Ry/e12T46dRg\n7tIlA/91rzLm57Qyc73A7zjgIzef9O6V5ZzowC+pp/jfb5pS9hXgROekLkMgX0vg\niA5rM5OpqK4bArVP1lRWnLyvghwO+TW763RVuXlS0scfzMy4g0NgrG6j7TIOKEqz\n4xQxOuwkudqiQr/kOqKuLxQBXa+5MJkyhfPmqYw5wpqyCwFa/7Q4b3NidWlsZCB0\nZXN0IChvc2J1aWxkIHRlc3QgZ3Bna2V5KSA8b3NidWlsZEBleGFtcGxlLmNvbT6I\newQTEQIAOxYhBGB8woiEPRKBO8Cr31lulpQgMejzBQJkQUiVAhsjBQsJCAcCAiIC\nBhUKCQgLAgQWAgMBAh4HAheAAAoJEFlulpQgMejzapMAoLmUg1mNDTRUaCrN/fzm\nHYLHL6jkAJ9pEKkJQiHB6SfD0fkiD2GkELYLubkBDQRkQUiVEAQAlAAXrQ572vuw\nxI3W8GSZmOQiAYOQmOKRloLEy6VZ3NSOb9y2TXj33QTkJBPOM17AzB7E+YjZrpUt\ngl6LlXmfjMcJAcXhFaUBCilAcMwMlLl7DtnSkLnLIXYmHiN0v83BH/H0EPutOc5l\n0QIyugutifp9SJz2+EWpC4bjA7GFkQ8AAwUD/1tLEGqCJ37O8gfzYt2PWkqBEoOY\n0Z3zwVS6PWW/IIkak9dAJ0iX5NMeFWpzFNfviDPHqhEdUR55zsxyUZIZlCX5jwmA\nt7qm3cbH4HNU1Ogq3Q9hykbTPWPZVkpvNm/TO8TA2brhkz3nuS8Hbmh+rjXFOSZj\nDQBUxItuuj2hhpQEiGAEGBECACAWIQRgfMKIhD0SgTvAq99ZbpaUIDHo8wUCZEFI\nlQIbDAAKCRBZbpaUIDHo83fQAKDHgFIaggaNsvDQkj7vMX0fecHRhACfS9Bvxn2W\nWSb6T+gChmYBseZwk/k=\n=DQ3i\n-----END PGP PUBLIC KEY BLOCK-----\n"
172172
]
173173
}
174-
]
174+
],
175+
"cacerts": {
176+
"pem_certs": [
177+
"-----BEGIN CERTIFICATE-----\nMIIDszCCApugAwIBAgIUJ4lK+JfdJCNgcEVxZDinJfKKbQswDQYJKoZIhvcNAQEL\nBQAwaDELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9saW5hMRAwDgYD\nVQQHDAdSYWxlaWdoMRAwDgYDVQQKDAdSZWQgSGF0MRwwGgYDVQQDDBNUZXN0IENB\nIGZvciBvc2J1aWxkMCAXDTI0MDkwMzEzMjkyMFoYDzIyOTgwNjE4MTMyOTIwWjBo\nMQswCQYDVQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExEDAOBgNVBAcM\nB1JhbGVpZ2gxEDAOBgNVBAoMB1JlZCBIYXQxHDAaBgNVBAMME1Rlc3QgQ0EgZm9y\nIG9zYnVpbGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDeA7OcWTrV\ngstoBsUaeJKm8nelg7Lc0WNXH6yOTLsr4td4yHs0YOvFGwgSf+ffV3RAG1mgqnMG\nMgkD2+z+7QhHbHHs3y0d0zfhA2bg0KVvfCWk7fNRPHY0UOePpXk245Bfw3D0VTpl\nF7nePk1I7ZY09snPWUeb2rjKXzYjKjzM0h27+ykV8I8+FbdyPk/pR8whyDqtHLUa\nXfFy2TFloDSYMkHKVd38BnL0bj91x5F+KsZkN4HzfbYwxLbCQfOSgy7q6TWce9kq\nLo6tya9vuvpWFm1dye7L+BodAQAq/dI/JMeCfyTb0eFb+tyzfr5aVIoqqDN+p9ft\ncw4OefpHbhtNAgMBAAGjUzBRMB0GA1UdDgQWBBRV2A9YmusekPzu5Yf08cV0oPL1\nwjAfBgNVHSMEGDAWgBRV2A9YmusekPzu5Yf08cV0oPL1wjAPBgNVHRMBAf8EBTAD\nAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCgQZ2Xfj+NxaKBZgn2KNxS0MTbhzHRz6Rn\nqJs+h8OUz2Crmaf6N+RHlmDRZXUrDjSHpxVT2LxFy7ofRrLYIezFDUYfb920VkkV\nSVcxh1YDFROJalfMoE6wdyR/LnK4MJZS9fUpeCJJc/A0J+9FK9CwcyUrHgJ8XbJh\nMKYyQ+cf6O7wzutuBpMyRqSKS+hVM7BQTmSFvv1eAJlo6klGAmmKiYmAEvcQadH1\ndjrujsA3Cn5vX2L+0yuiLB5/zoxqx5cEy97TuKUYB8OqMMujAXNzF4L3HJDUNba2\nAhEkFozMXwYX73TGbGZ0mawPS5D3v3tYTEmJFf6SnVCmUW1fs57g\n-----END CERTIFICATE-----\n"
178+
]
179+
}
175180
}
176181
}
177182
}

0 commit comments

Comments
 (0)