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 }