package webrtc
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/pem"
"fmt"
"math/big"
"strings"
"time"
"github.com/pion/dtls/v3/pkg/crypto/fingerprint"
"github.com/pion/webrtc/v4/pkg/rtcerr"
)
type Certificate struct {
privateKey crypto .PrivateKey
x509Cert *x509 .Certificate
statsID string
}
func NewCertificate (key crypto .PrivateKey , tpl x509 .Certificate ) (*Certificate , error ) {
var err error
var certDER []byte
switch sk := key .(type ) {
case *rsa .PrivateKey :
pk := sk .Public ()
tpl .SignatureAlgorithm = x509 .SHA256WithRSA
certDER , err = x509 .CreateCertificate (rand .Reader , &tpl , &tpl , pk , sk )
if err != nil {
return nil , &rtcerr .UnknownError {Err : err }
}
case *ecdsa .PrivateKey :
pk := sk .Public ()
tpl .SignatureAlgorithm = x509 .ECDSAWithSHA256
certDER , err = x509 .CreateCertificate (rand .Reader , &tpl , &tpl , pk , sk )
if err != nil {
return nil , &rtcerr .UnknownError {Err : err }
}
default :
return nil , &rtcerr .NotSupportedError {Err : ErrPrivateKeyType }
}
cert , err := x509 .ParseCertificate (certDER )
if err != nil {
return nil , &rtcerr .UnknownError {Err : err }
}
return &Certificate {
privateKey : key ,
x509Cert : cert ,
statsID : fmt .Sprintf ("certificate-%d" , time .Now ().UnixNano ()),
}, nil
}
func (c Certificate ) Equals (cert Certificate ) bool {
switch cSK := c .privateKey .(type ) {
case *rsa .PrivateKey :
if oSK , ok := cert .privateKey .(*rsa .PrivateKey ); ok {
if cSK .N .Cmp (oSK .N ) != 0 {
return false
}
return c .x509Cert .Equal (cert .x509Cert )
}
return false
case *ecdsa .PrivateKey :
if oSK , ok := cert .privateKey .(*ecdsa .PrivateKey ); ok {
if cSK .X .Cmp (oSK .X ) != 0 || cSK .Y .Cmp (oSK .Y ) != 0 {
return false
}
return c .x509Cert .Equal (cert .x509Cert )
}
return false
default :
return false
}
}
func (c Certificate ) Expires () time .Time {
if c .x509Cert == nil {
return time .Time {}
}
return c .x509Cert .NotAfter
}
func (c Certificate ) GetFingerprints () ([]DTLSFingerprint , error ) {
fingerprintAlgorithms := []crypto .Hash {crypto .SHA256 }
res := make ([]DTLSFingerprint , len (fingerprintAlgorithms ))
i := 0
for _ , algo := range fingerprintAlgorithms {
name , err := fingerprint .StringFromHash (algo )
if err != nil {
return nil , fmt .Errorf ("%w: %v" , ErrFailedToGenerateCertificateFingerprint , err )
}
value , err := fingerprint .Fingerprint (c .x509Cert , algo )
if err != nil {
return nil , fmt .Errorf ("%w: %v" , ErrFailedToGenerateCertificateFingerprint , err )
}
res [i ] = DTLSFingerprint {
Algorithm : name ,
Value : value ,
}
}
return res [:i +1 ], nil
}
func GenerateCertificate (secretKey crypto .PrivateKey ) (*Certificate , error ) {
maxBigInt := new (big .Int )
maxBigInt .Exp (big .NewInt (2 ), big .NewInt (130 ), nil ).Sub (maxBigInt , big .NewInt (1 ))
serialNumber , err := rand .Int (rand .Reader , maxBigInt )
if err != nil {
return nil , &rtcerr .UnknownError {Err : err }
}
return NewCertificate (secretKey , x509 .Certificate {
Issuer : pkix .Name {CommonName : generatedCertificateOrigin },
NotBefore : time .Now ().AddDate (0 , 0 , -1 ),
NotAfter : time .Now ().AddDate (0 , 1 , -1 ),
SerialNumber : serialNumber ,
Version : 2 ,
Subject : pkix .Name {CommonName : generatedCertificateOrigin },
})
}
func CertificateFromX509 (privateKey crypto .PrivateKey , certificate *x509 .Certificate ) Certificate {
return Certificate {privateKey , certificate , fmt .Sprintf ("certificate-%d" , time .Now ().UnixNano ())}
}
func (c Certificate ) collectStats (report *statsReportCollector ) error {
report .Collecting ()
fingerPrintAlgo , err := c .GetFingerprints ()
if err != nil {
return err
}
base64Certificate := base64 .RawURLEncoding .EncodeToString (c .x509Cert .Raw )
stats := CertificateStats {
Timestamp : statsTimestampFrom (time .Now ()),
Type : StatsTypeCertificate ,
ID : c .statsID ,
Fingerprint : fingerPrintAlgo [0 ].Value ,
FingerprintAlgorithm : fingerPrintAlgo [0 ].Algorithm ,
Base64Certificate : base64Certificate ,
IssuerCertificateID : c .x509Cert .Issuer .String (),
}
report .Collect (stats .ID , stats )
return nil
}
func CertificateFromPEM (pems string ) (*Certificate , error ) {
var cert *x509 .Certificate
var privateKey crypto .PrivateKey
var block *pem .Block
more := []byte (pems )
for {
var err error
block , more = pem .Decode (more )
if block == nil {
break
}
switch block .Type {
case "CERTIFICATE" :
if cert != nil {
return nil , errCertificatePEMMultipleCert
}
cert , err = x509 .ParseCertificate (block .Bytes )
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 )
}
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
}
func (c Certificate ) PEM () (string , error ) {
var builder strings .Builder
xcertBytes := make (
[]byte , base64 .StdEncoding .EncodedLen (len (c .x509Cert .Raw )))
base64 .StdEncoding .Encode (xcertBytes , c .x509Cert .Raw )
err := pem .Encode (&builder , &pem .Block {Type : "CERTIFICATE" , Bytes : xcertBytes })
if err != nil {
return "" , fmt .Errorf ("failed to pem encode the X certificate: %w" , err )
}
privBytes , err := x509 .MarshalPKCS8PrivateKey (c .privateKey )
if err != nil {
return "" , fmt .Errorf ("failed to marshal private key: %w" , err )
}
err = pem .Encode (&builder , &pem .Block {Type : "PRIVATE KEY" , Bytes : privBytes })
if err != nil {
return "" , fmt .Errorf ("failed to encode private key: %w" , err )
}
return builder .String (), nil
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .