diff --git a/certificate.go b/certificate.go index dfceb9b1..f2c23d4c 100644 --- a/certificate.go +++ b/certificate.go @@ -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 diff --git a/certificate_test.go b/certificate_test.go index a39e52e0..58f24d8d 100644 --- a/certificate_test.go +++ b/certificate_test.go @@ -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) +} diff --git a/errors.go b/errors.go index ab428925..4bf9fe81 100644 --- a/errors.go +++ b/errors.go @@ -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")