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
This commit is contained in:
Evan Brass
2025-03-09 09:44:10 -07:00
committed by Sean DuBois
parent e4ff415b2b
commit 34f547626b
3 changed files with 105 additions and 27 deletions

View File

@@ -191,33 +191,55 @@ func (c Certificate) collectStats(report *statsReportCollector) error {
// CertificateFromPEM creates a fresh certificate based on a string containing
// pem blocks fort the private key and x509 certificate.
func CertificateFromPEM(pems string) (*Certificate, error) {
// decode & parse the certificate
block, more := pem.Decode([]byte(pems))
if block == nil || block.Type != "CERTIFICATE" {
return nil, errCertificatePEMFormatError
}
certBytes := make([]byte, base64.StdEncoding.DecodedLen(len(block.Bytes)))
n, err := base64.StdEncoding.Decode(certBytes, block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to decode ceritifcate: %w", err)
}
cert, err := x509.ParseCertificate(certBytes[:n])
if err != nil {
return nil, fmt.Errorf("failed parsing ceritifcate: %w", err)
}
// decode & parse the private key
block, _ = pem.Decode(more)
if block == nil || block.Type != "PRIVATE KEY" {
return nil, errCertificatePEMFormatError
}
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("unable to parse private key: %w", err)
}
x := CertificateFromX509(privateKey, cert)
func CertificateFromPEM(pems string) (*Certificate, error) { //nolint: cyclop
var cert *x509.Certificate
var privateKey crypto.PrivateKey
return &x, nil
var block *pem.Block
more := []byte(pems)
for {
var err error
block, more = pem.Decode(more)
if block == nil {
break
}
// decode & parse the certificate
switch block.Type {
case "CERTIFICATE":
if cert != nil {
return nil, errCertificatePEMMultipleCert
}
cert, err = x509.ParseCertificate(block.Bytes)
// If parsing failed using block.Bytes, then parse the bytes as base64 and try again
if err != nil {
var n int
certBytes := make([]byte, base64.StdEncoding.DecodedLen(len(block.Bytes)))
n, err = base64.StdEncoding.Decode(certBytes, block.Bytes)
if err == nil {
cert, err = x509.ParseCertificate(certBytes[:n])
}
}
case "PRIVATE KEY":
if privateKey != nil {
return nil, errCertificatePEMMultiplePriv
}
privateKey, err = x509.ParsePKCS8PrivateKey(block.Bytes)
}
// Report errors from parsing either the private key or the certificate
if err != nil {
return nil, fmt.Errorf("failed to decode %s: %w", block.Type, err)
}
}
if cert == nil || privateKey == nil {
return nil, errCertificatePEMMissing
}
ret := CertificateFromX509(privateKey, cert)
return &ret, nil
}
// PEM returns the certificate encoded as two pem block: once for the X509

View File

@@ -136,3 +136,57 @@ func TestPEM(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, pem, pem2)
}
const (
certHeader = `!! This is a test certificate: Don't use it in production !!
You can create your own using openssl
` + "```sh" + `
openssl req -new -sha256 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 ` +
`-x509 -nodes -days 365 -out cert.pem -keyout cert.pem -subj "/CN=WebRTC"
openssl x509 -in cert.pem -noout -fingerprint -sha256
` + "```\n"
certPriv = `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg2XFaTNqFpTUqNtG9
A21MEe04JtsWVpUTDD8nI0KvchKhRANCAAS1nqME3jS5GFicwYfGDYaz7oSINwWm
X4BkfsSCxMrhr7mPtfxOi4Lxy/P3w6EvSSEU8t5E9ouKIWh5xPS9dYwu
-----END PRIVATE KEY-----
`
certCert = `-----BEGIN CERTIFICATE-----
MIIBljCCATugAwIBAgIUQa1sD+5HG43K+hCEVZLYxB68/hQwCgYIKoZIzj0EAwIw
IDEeMBwGA1UEAwwVc3dpdGNoLmV2YW4tYnJhc3MubmV0MB4XDTI0MDQyNDIwMjEy
MFoXDTI1MDQyNDIwMjEyMFowIDEeMBwGA1UEAwwVc3dpdGNoLmV2YW4tYnJhc3Mu
bmV0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtZ6jBN40uRhYnMGHxg2Gs+6E
iDcFpl+AZH7EgsTK4a+5j7X8TouC8cvz98OhL0khFPLeRPaLiiFoecT0vXWMLqNT
MFEwHQYDVR0OBBYEFGecfGnYqZFVgUApHGgX2kSIhUusMB8GA1UdIwQYMBaAFGec
fGnYqZFVgUApHGgX2kSIhUusMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwID
SQAwRgIhAJ3VWO8JZ7FEOJhxpUCeyOgl+G4vXSHtj9J9NRD3uGGZAiEAsTKGLOGE
9c6CtLDU9Ohf1c+Xj2Yi9H+srLZj1mrsnd4=
-----END CERTIFICATE-----
`
)
func TestOpensslCert(t *testing.T) {
// Check that CertificateFromPEM can parse certificates with the PRIVATE KEY before the CERTIFICATE block
_, err := CertificateFromPEM(certHeader + certPriv + certCert)
assert.Nil(t, err)
}
func TestEmpty(t *testing.T) {
cert, err := CertificateFromPEM("")
assert.Nil(t, cert)
assert.Equal(t, errCertificatePEMMissing, err)
}
func TestMultiCert(t *testing.T) {
cert, err := CertificateFromPEM(certHeader + certCert + certPriv + certCert)
assert.Nil(t, cert)
assert.Equal(t, errCertificatePEMMultipleCert, err)
}
func TestMultiPriv(t *testing.T) {
cert, err := CertificateFromPEM(certPriv + certHeader + certCert + certPriv)
assert.Nil(t, cert)
assert.Equal(t, errCertificatePEMMultiplePriv, err)
}

View File

@@ -275,7 +275,9 @@ var (
errICETransportNotInNew = errors.New("ICETransport can only be called in ICETransportStateNew")
errICETransportClosed = errors.New("ICETransport closed")
errCertificatePEMFormatError = errors.New("bad Certificate PEM format")
errCertificatePEMMultipleCert = errors.New("failed parsing certificate, more than 1 CERTIFICATE block in pems")
errCertificatePEMMultiplePriv = errors.New("failed parsing certificate, more than 1 PRIVATE KEY block in pems")
errCertificatePEMMissing = errors.New("failed parsing certificate, pems must contain both a CERTIFICATE block and a PRIVATE KEY block") // nolint: lll
errRTPTooShort = errors.New("not long enough to be a RTP Packet")