github.com/pion/webrtc/v4@v4.0.1/certificate.go (about)

     1  // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
     2  // SPDX-License-Identifier: MIT
     3  
     4  //go:build !js
     5  // +build !js
     6  
     7  package webrtc
     8  
     9  import (
    10  	"crypto"
    11  	"crypto/ecdsa"
    12  	"crypto/rand"
    13  	"crypto/rsa"
    14  	"crypto/x509"
    15  	"crypto/x509/pkix"
    16  	"encoding/base64"
    17  	"encoding/pem"
    18  	"fmt"
    19  	"math/big"
    20  	"strings"
    21  	"time"
    22  
    23  	"github.com/pion/dtls/v3/pkg/crypto/fingerprint"
    24  	"github.com/pion/webrtc/v4/pkg/rtcerr"
    25  )
    26  
    27  // Certificate represents a x509Cert used to authenticate WebRTC communications.
    28  type Certificate struct {
    29  	privateKey crypto.PrivateKey
    30  	x509Cert   *x509.Certificate
    31  	statsID    string
    32  }
    33  
    34  // NewCertificate generates a new x509 compliant Certificate to be used
    35  // by DTLS for encrypting data sent over the wire. This method differs from
    36  // GenerateCertificate by allowing to specify a template x509.Certificate to
    37  // be used in order to define certificate parameters.
    38  func NewCertificate(key crypto.PrivateKey, tpl x509.Certificate) (*Certificate, error) {
    39  	var err error
    40  	var certDER []byte
    41  	switch sk := key.(type) {
    42  	case *rsa.PrivateKey:
    43  		pk := sk.Public()
    44  		tpl.SignatureAlgorithm = x509.SHA256WithRSA
    45  		certDER, err = x509.CreateCertificate(rand.Reader, &tpl, &tpl, pk, sk)
    46  		if err != nil {
    47  			return nil, &rtcerr.UnknownError{Err: err}
    48  		}
    49  	case *ecdsa.PrivateKey:
    50  		pk := sk.Public()
    51  		tpl.SignatureAlgorithm = x509.ECDSAWithSHA256
    52  		certDER, err = x509.CreateCertificate(rand.Reader, &tpl, &tpl, pk, sk)
    53  		if err != nil {
    54  			return nil, &rtcerr.UnknownError{Err: err}
    55  		}
    56  	default:
    57  		return nil, &rtcerr.NotSupportedError{Err: ErrPrivateKeyType}
    58  	}
    59  
    60  	cert, err := x509.ParseCertificate(certDER)
    61  	if err != nil {
    62  		return nil, &rtcerr.UnknownError{Err: err}
    63  	}
    64  
    65  	return &Certificate{privateKey: key, x509Cert: cert, statsID: fmt.Sprintf("certificate-%d", time.Now().UnixNano())}, nil
    66  }
    67  
    68  // Equals determines if two certificates are identical by comparing both the
    69  // secretKeys and x509Certificates.
    70  func (c Certificate) Equals(o Certificate) bool {
    71  	switch cSK := c.privateKey.(type) {
    72  	case *rsa.PrivateKey:
    73  		if oSK, ok := o.privateKey.(*rsa.PrivateKey); ok {
    74  			if cSK.N.Cmp(oSK.N) != 0 {
    75  				return false
    76  			}
    77  			return c.x509Cert.Equal(o.x509Cert)
    78  		}
    79  		return false
    80  	case *ecdsa.PrivateKey:
    81  		if oSK, ok := o.privateKey.(*ecdsa.PrivateKey); ok {
    82  			if cSK.X.Cmp(oSK.X) != 0 || cSK.Y.Cmp(oSK.Y) != 0 {
    83  				return false
    84  			}
    85  			return c.x509Cert.Equal(o.x509Cert)
    86  		}
    87  		return false
    88  	default:
    89  		return false
    90  	}
    91  }
    92  
    93  // Expires returns the timestamp after which this certificate is no longer valid.
    94  func (c Certificate) Expires() time.Time {
    95  	if c.x509Cert == nil {
    96  		return time.Time{}
    97  	}
    98  	return c.x509Cert.NotAfter
    99  }
   100  
   101  // GetFingerprints returns the list of certificate fingerprints, one of which
   102  // is computed with the digest algorithm used in the certificate signature.
   103  func (c Certificate) GetFingerprints() ([]DTLSFingerprint, error) {
   104  	fingerprintAlgorithms := []crypto.Hash{crypto.SHA256}
   105  	res := make([]DTLSFingerprint, len(fingerprintAlgorithms))
   106  
   107  	i := 0
   108  	for _, algo := range fingerprintAlgorithms {
   109  		name, err := fingerprint.StringFromHash(algo)
   110  		if err != nil {
   111  			// nolint
   112  			return nil, fmt.Errorf("%w: %v", ErrFailedToGenerateCertificateFingerprint, err)
   113  		}
   114  		value, err := fingerprint.Fingerprint(c.x509Cert, algo)
   115  		if err != nil {
   116  			// nolint
   117  			return nil, fmt.Errorf("%w: %v", ErrFailedToGenerateCertificateFingerprint, err)
   118  		}
   119  		res[i] = DTLSFingerprint{
   120  			Algorithm: name,
   121  			Value:     value,
   122  		}
   123  	}
   124  
   125  	return res[:i+1], nil
   126  }
   127  
   128  // GenerateCertificate causes the creation of an X.509 certificate and
   129  // corresponding private key.
   130  func GenerateCertificate(secretKey crypto.PrivateKey) (*Certificate, error) {
   131  	// Max random value, a 130-bits integer, i.e 2^130 - 1
   132  	maxBigInt := new(big.Int)
   133  	/* #nosec */
   134  	maxBigInt.Exp(big.NewInt(2), big.NewInt(130), nil).Sub(maxBigInt, big.NewInt(1))
   135  	/* #nosec */
   136  	serialNumber, err := rand.Int(rand.Reader, maxBigInt)
   137  	if err != nil {
   138  		return nil, &rtcerr.UnknownError{Err: err}
   139  	}
   140  
   141  	return NewCertificate(secretKey, x509.Certificate{
   142  		Issuer:       pkix.Name{CommonName: generatedCertificateOrigin},
   143  		NotBefore:    time.Now().AddDate(0, 0, -1),
   144  		NotAfter:     time.Now().AddDate(0, 1, -1),
   145  		SerialNumber: serialNumber,
   146  		Version:      2,
   147  		Subject:      pkix.Name{CommonName: generatedCertificateOrigin},
   148  	})
   149  }
   150  
   151  // CertificateFromX509 creates a new WebRTC Certificate from a given PrivateKey and Certificate
   152  //
   153  // This can be used if you want to share a certificate across multiple PeerConnections
   154  func CertificateFromX509(privateKey crypto.PrivateKey, certificate *x509.Certificate) Certificate {
   155  	return Certificate{privateKey, certificate, fmt.Sprintf("certificate-%d", time.Now().UnixNano())}
   156  }
   157  
   158  func (c Certificate) collectStats(report *statsReportCollector) error {
   159  	report.Collecting()
   160  
   161  	fingerPrintAlgo, err := c.GetFingerprints()
   162  	if err != nil {
   163  		return err
   164  	}
   165  
   166  	base64Certificate := base64.RawURLEncoding.EncodeToString(c.x509Cert.Raw)
   167  
   168  	stats := CertificateStats{
   169  		Timestamp:            statsTimestampFrom(time.Now()),
   170  		Type:                 StatsTypeCertificate,
   171  		ID:                   c.statsID,
   172  		Fingerprint:          fingerPrintAlgo[0].Value,
   173  		FingerprintAlgorithm: fingerPrintAlgo[0].Algorithm,
   174  		Base64Certificate:    base64Certificate,
   175  		IssuerCertificateID:  c.x509Cert.Issuer.String(),
   176  	}
   177  
   178  	report.Collect(stats.ID, stats)
   179  	return nil
   180  }
   181  
   182  // CertificateFromPEM creates a fresh certificate based on a string containing
   183  // pem blocks fort the private key and x509 certificate
   184  func CertificateFromPEM(pems string) (*Certificate, error) {
   185  	// decode & parse the certificate
   186  	block, more := pem.Decode([]byte(pems))
   187  	if block == nil || block.Type != "CERTIFICATE" {
   188  		return nil, errCertificatePEMFormatError
   189  	}
   190  	certBytes := make([]byte, base64.StdEncoding.DecodedLen(len(block.Bytes)))
   191  	n, err := base64.StdEncoding.Decode(certBytes, block.Bytes)
   192  	if err != nil {
   193  		return nil, fmt.Errorf("failed to decode ceritifcate: %w", err)
   194  	}
   195  	cert, err := x509.ParseCertificate(certBytes[:n])
   196  	if err != nil {
   197  		return nil, fmt.Errorf("failed parsing ceritifcate: %w", err)
   198  	}
   199  	// decode & parse the private key
   200  	block, _ = pem.Decode(more)
   201  	if block == nil || block.Type != "PRIVATE KEY" {
   202  		return nil, errCertificatePEMFormatError
   203  	}
   204  	privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
   205  	if err != nil {
   206  		return nil, fmt.Errorf("unable to parse private key: %w", err)
   207  	}
   208  	x := CertificateFromX509(privateKey, cert)
   209  	return &x, nil
   210  }
   211  
   212  // PEM returns the certificate encoded as two pem block: once for the X509
   213  // certificate and the other for the private key
   214  func (c Certificate) PEM() (string, error) {
   215  	// First write the X509 certificate
   216  	var o strings.Builder
   217  	xcertBytes := make(
   218  		[]byte, base64.StdEncoding.EncodedLen(len(c.x509Cert.Raw)))
   219  	base64.StdEncoding.Encode(xcertBytes, c.x509Cert.Raw)
   220  	err := pem.Encode(&o, &pem.Block{Type: "CERTIFICATE", Bytes: xcertBytes})
   221  	if err != nil {
   222  		return "", fmt.Errorf("failed to pem encode the X certificate: %w", err)
   223  	}
   224  	// Next write the private key
   225  	privBytes, err := x509.MarshalPKCS8PrivateKey(c.privateKey)
   226  	if err != nil {
   227  		return "", fmt.Errorf("failed to marshal private key: %w", err)
   228  	}
   229  	err = pem.Encode(&o, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
   230  	if err != nil {
   231  		return "", fmt.Errorf("failed to encode private key: %w", err)
   232  	}
   233  	return o.String(), nil
   234  }