Skip to content

Commit 34f5476

Browse files
evan-brassSean-Der
authored andcommitted
Update CertificateFromPEM to support OpenSSL
Update CertificateFromPEM to be a loop over the PEM blocks. This allows decoding the private key before decoding the certificate. Tries to parse the certificate block directly but if that errors, then it also tries to base64 decode the certificate block. Fixes #3042
1 parent e4ff415 commit 34f5476

File tree

3 files changed

+103
-25
lines changed

3 files changed

+103
-25
lines changed

certificate.go

+46-24
Original file line numberDiff line numberDiff line change
@@ -191,33 +191,55 @@ func (c Certificate) collectStats(report *statsReportCollector) error {
191191

192192
// CertificateFromPEM creates a fresh certificate based on a string containing
193193
// pem blocks fort the private key and x509 certificate.
194-
func CertificateFromPEM(pems string) (*Certificate, error) {
195-
// decode & parse the certificate
196-
block, more := pem.Decode([]byte(pems))
197-
if block == nil || block.Type != "CERTIFICATE" {
198-
return nil, errCertificatePEMFormatError
199-
}
200-
certBytes := make([]byte, base64.StdEncoding.DecodedLen(len(block.Bytes)))
201-
n, err := base64.StdEncoding.Decode(certBytes, block.Bytes)
202-
if err != nil {
203-
return nil, fmt.Errorf("failed to decode ceritifcate: %w", err)
204-
}
205-
cert, err := x509.ParseCertificate(certBytes[:n])
206-
if err != nil {
207-
return nil, fmt.Errorf("failed parsing ceritifcate: %w", err)
208-
}
209-
// decode & parse the private key
210-
block, _ = pem.Decode(more)
211-
if block == nil || block.Type != "PRIVATE KEY" {
212-
return nil, errCertificatePEMFormatError
194+
func CertificateFromPEM(pems string) (*Certificate, error) { //nolint: cyclop
195+
var cert *x509.Certificate
196+
var privateKey crypto.PrivateKey
197+
198+
var block *pem.Block
199+
more := []byte(pems)
200+
for {
201+
var err error
202+
block, more = pem.Decode(more)
203+
if block == nil {
204+
break
205+
}
206+
207+
// decode & parse the certificate
208+
switch block.Type {
209+
case "CERTIFICATE":
210+
if cert != nil {
211+
return nil, errCertificatePEMMultipleCert
212+
}
213+
cert, err = x509.ParseCertificate(block.Bytes)
214+
// If parsing failed using block.Bytes, then parse the bytes as base64 and try again
215+
if err != nil {
216+
var n int
217+
certBytes := make([]byte, base64.StdEncoding.DecodedLen(len(block.Bytes)))
218+
n, err = base64.StdEncoding.Decode(certBytes, block.Bytes)
219+
if err == nil {
220+
cert, err = x509.ParseCertificate(certBytes[:n])
221+
}
222+
}
223+
case "PRIVATE KEY":
224+
if privateKey != nil {
225+
return nil, errCertificatePEMMultiplePriv
226+
}
227+
privateKey, err = x509.ParsePKCS8PrivateKey(block.Bytes)
228+
}
229+
230+
// Report errors from parsing either the private key or the certificate
231+
if err != nil {
232+
return nil, fmt.Errorf("failed to decode %s: %w", block.Type, err)
233+
}
213234
}
214-
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
215-
if err != nil {
216-
return nil, fmt.Errorf("unable to parse private key: %w", err)
235+
236+
if cert == nil || privateKey == nil {
237+
return nil, errCertificatePEMMissing
217238
}
218-
x := CertificateFromX509(privateKey, cert)
219239

220-
return &x, nil
240+
ret := CertificateFromX509(privateKey, cert)
241+
242+
return &ret, nil
221243
}
222244

223245
// PEM returns the certificate encoded as two pem block: once for the X509

certificate_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,57 @@ func TestPEM(t *testing.T) {
136136
assert.Nil(t, err)
137137
assert.Equal(t, pem, pem2)
138138
}
139+
140+
const (
141+
certHeader = `!! This is a test certificate: Don't use it in production !!
142+
You can create your own using openssl
143+
` + "```sh" + `
144+
openssl req -new -sha256 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 ` +
145+
`-x509 -nodes -days 365 -out cert.pem -keyout cert.pem -subj "/CN=WebRTC"
146+
openssl x509 -in cert.pem -noout -fingerprint -sha256
147+
` + "```\n"
148+
149+
certPriv = `-----BEGIN PRIVATE KEY-----
150+
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg2XFaTNqFpTUqNtG9
151+
A21MEe04JtsWVpUTDD8nI0KvchKhRANCAAS1nqME3jS5GFicwYfGDYaz7oSINwWm
152+
X4BkfsSCxMrhr7mPtfxOi4Lxy/P3w6EvSSEU8t5E9ouKIWh5xPS9dYwu
153+
-----END PRIVATE KEY-----
154+
`
155+
156+
certCert = `-----BEGIN CERTIFICATE-----
157+
MIIBljCCATugAwIBAgIUQa1sD+5HG43K+hCEVZLYxB68/hQwCgYIKoZIzj0EAwIw
158+
IDEeMBwGA1UEAwwVc3dpdGNoLmV2YW4tYnJhc3MubmV0MB4XDTI0MDQyNDIwMjEy
159+
MFoXDTI1MDQyNDIwMjEyMFowIDEeMBwGA1UEAwwVc3dpdGNoLmV2YW4tYnJhc3Mu
160+
bmV0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtZ6jBN40uRhYnMGHxg2Gs+6E
161+
iDcFpl+AZH7EgsTK4a+5j7X8TouC8cvz98OhL0khFPLeRPaLiiFoecT0vXWMLqNT
162+
MFEwHQYDVR0OBBYEFGecfGnYqZFVgUApHGgX2kSIhUusMB8GA1UdIwQYMBaAFGec
163+
fGnYqZFVgUApHGgX2kSIhUusMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwID
164+
SQAwRgIhAJ3VWO8JZ7FEOJhxpUCeyOgl+G4vXSHtj9J9NRD3uGGZAiEAsTKGLOGE
165+
9c6CtLDU9Ohf1c+Xj2Yi9H+srLZj1mrsnd4=
166+
-----END CERTIFICATE-----
167+
`
168+
)
169+
170+
func TestOpensslCert(t *testing.T) {
171+
// Check that CertificateFromPEM can parse certificates with the PRIVATE KEY before the CERTIFICATE block
172+
_, err := CertificateFromPEM(certHeader + certPriv + certCert)
173+
assert.Nil(t, err)
174+
}
175+
176+
func TestEmpty(t *testing.T) {
177+
cert, err := CertificateFromPEM("")
178+
assert.Nil(t, cert)
179+
assert.Equal(t, errCertificatePEMMissing, err)
180+
}
181+
182+
func TestMultiCert(t *testing.T) {
183+
cert, err := CertificateFromPEM(certHeader + certCert + certPriv + certCert)
184+
assert.Nil(t, cert)
185+
assert.Equal(t, errCertificatePEMMultipleCert, err)
186+
}
187+
188+
func TestMultiPriv(t *testing.T) {
189+
cert, err := CertificateFromPEM(certPriv + certHeader + certCert + certPriv)
190+
assert.Nil(t, cert)
191+
assert.Equal(t, errCertificatePEMMultiplePriv, err)
192+
}

errors.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,9 @@ var (
275275
errICETransportNotInNew = errors.New("ICETransport can only be called in ICETransportStateNew")
276276
errICETransportClosed = errors.New("ICETransport closed")
277277

278-
errCertificatePEMFormatError = errors.New("bad Certificate PEM format")
278+
errCertificatePEMMultipleCert = errors.New("failed parsing certificate, more than 1 CERTIFICATE block in pems")
279+
errCertificatePEMMultiplePriv = errors.New("failed parsing certificate, more than 1 PRIVATE KEY block in pems")
280+
errCertificatePEMMissing = errors.New("failed parsing certificate, pems must contain both a CERTIFICATE block and a PRIVATE KEY block") // nolint: lll
279281

280282
errRTPTooShort = errors.New("not long enough to be a RTP Packet")
281283

0 commit comments

Comments
 (0)