// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package webrtc

import (
	
	
	
	
	
	
	
	
	
	
	
	

	
	
)

// Certificate represents a x509Cert used to authenticate WebRTC communications.
type Certificate struct {
	privateKey crypto.PrivateKey
	x509Cert   *x509.Certificate
	statsID    string
}

// NewCertificate generates a new x509 compliant Certificate to be used
// by DTLS for encrypting data sent over the wire. This method differs from
// GenerateCertificate by allowing to specify a template x509.Certificate to
// be used in order to define certificate parameters.
func ( crypto.PrivateKey,  x509.Certificate) (*Certificate, error) {
	var  error
	var  []byte
	switch sk := .(type) {
	case *rsa.PrivateKey:
		 := .Public()
		.SignatureAlgorithm = x509.SHA256WithRSA
		,  = x509.CreateCertificate(rand.Reader, &, &, , )
		if  != nil {
			return nil, &rtcerr.UnknownError{Err: }
		}
	case *ecdsa.PrivateKey:
		 := .Public()
		.SignatureAlgorithm = x509.ECDSAWithSHA256
		,  = x509.CreateCertificate(rand.Reader, &, &, , )
		if  != nil {
			return nil, &rtcerr.UnknownError{Err: }
		}
	default:
		return nil, &rtcerr.NotSupportedError{Err: ErrPrivateKeyType}
	}

	,  := x509.ParseCertificate()
	if  != nil {
		return nil, &rtcerr.UnknownError{Err: }
	}

	return &Certificate{
		privateKey: ,
		x509Cert:   ,
		statsID:    fmt.Sprintf("certificate-%d", time.Now().UnixNano()),
	}, nil
}

// Equals determines if two certificates are identical by comparing both the
// secretKeys and x509Certificates.
func ( Certificate) ( Certificate) bool {
	switch cSK := .privateKey.(type) {
	case *rsa.PrivateKey:
		if ,  := .privateKey.(*rsa.PrivateKey);  {
			if .N.Cmp(.N) != 0 {
				return false
			}

			return .x509Cert.Equal(.x509Cert)
		}

		return false
	case *ecdsa.PrivateKey:
		if ,  := .privateKey.(*ecdsa.PrivateKey);  {
			if .X.Cmp(.X) != 0 || .Y.Cmp(.Y) != 0 {
				return false
			}

			return .x509Cert.Equal(.x509Cert)
		}

		return false
	default:
		return false
	}
}

// Expires returns the timestamp after which this certificate is no longer valid.
func ( Certificate) () time.Time {
	if .x509Cert == nil {
		return time.Time{}
	}

	return .x509Cert.NotAfter
}

// GetFingerprints returns the list of certificate fingerprints, one of which
// is computed with the digest algorithm used in the certificate signature.
func ( Certificate) () ([]DTLSFingerprint, error) {
	 := []crypto.Hash{crypto.SHA256}
	 := make([]DTLSFingerprint, len())

	 := 0
	for ,  := range  {
		,  := fingerprint.StringFromHash()
		if  != nil {
			// nolint
			return nil, fmt.Errorf("%w: %v", ErrFailedToGenerateCertificateFingerprint, )
		}
		,  := fingerprint.Fingerprint(.x509Cert, )
		if  != nil {
			// nolint
			return nil, fmt.Errorf("%w: %v", ErrFailedToGenerateCertificateFingerprint, )
		}
		[] = DTLSFingerprint{
			Algorithm: ,
			Value:     ,
		}
	}

	return [:+1], nil
}

// GenerateCertificate causes the creation of an X.509 certificate and
// corresponding private key.
func ( crypto.PrivateKey) (*Certificate, error) {
	// Max random value, a 130-bits integer, i.e 2^130 - 1
	 := new(big.Int)
	/* #nosec */
	.Exp(big.NewInt(2), big.NewInt(130), nil).Sub(, big.NewInt(1))
	/* #nosec */
	,  := rand.Int(rand.Reader, )
	if  != nil {
		return nil, &rtcerr.UnknownError{Err: }
	}

	return NewCertificate(, x509.Certificate{
		Issuer:       pkix.Name{CommonName: generatedCertificateOrigin},
		NotBefore:    time.Now().AddDate(0, 0, -1),
		NotAfter:     time.Now().AddDate(0, 1, -1),
		SerialNumber: ,
		Version:      2,
		Subject:      pkix.Name{CommonName: generatedCertificateOrigin},
	})
}

// CertificateFromX509 creates a new WebRTC Certificate from a given PrivateKey and Certificate
//
// This can be used if you want to share a certificate across multiple PeerConnections.
func ( crypto.PrivateKey,  *x509.Certificate) Certificate {
	return Certificate{, , fmt.Sprintf("certificate-%d", time.Now().UnixNano())}
}

func ( Certificate) ( *statsReportCollector) error {
	.Collecting()

	,  := .GetFingerprints()
	if  != nil {
		return 
	}

	 := base64.RawURLEncoding.EncodeToString(.x509Cert.Raw)

	 := CertificateStats{
		Timestamp:            statsTimestampFrom(time.Now()),
		Type:                 StatsTypeCertificate,
		ID:                   .statsID,
		Fingerprint:          [0].Value,
		FingerprintAlgorithm: [0].Algorithm,
		Base64Certificate:    ,
		IssuerCertificateID:  .x509Cert.Issuer.String(),
	}

	.Collect(.ID, )

	return nil
}

// CertificateFromPEM creates a fresh certificate based on a string containing
// pem blocks fort the private key and x509 certificate.
func ( string) (*Certificate, error) { //nolint: cyclop
	var  *x509.Certificate
	var  crypto.PrivateKey

	var  *pem.Block
	 := []byte()
	for {
		var  error
		,  = pem.Decode()
		if  == nil {
			break
		}

		// decode & parse the certificate
		switch .Type {
		case "CERTIFICATE":
			if  != nil {
				return nil, errCertificatePEMMultipleCert
			}
			,  = x509.ParseCertificate(.Bytes)
			// If parsing failed using block.Bytes, then parse the bytes as base64 and try again
			if  != nil {
				var  int
				 := make([]byte, base64.StdEncoding.DecodedLen(len(.Bytes)))
				,  = base64.StdEncoding.Decode(, .Bytes)
				if  == nil {
					,  = x509.ParseCertificate([:])
				}
			}
		case "PRIVATE KEY":
			if  != nil {
				return nil, errCertificatePEMMultiplePriv
			}
			,  = x509.ParsePKCS8PrivateKey(.Bytes)
		}

		// Report errors from parsing either the private key or the certificate
		if  != nil {
			return nil, fmt.Errorf("failed to decode %s: %w", .Type, )
		}
	}

	if  == nil ||  == nil {
		return nil, errCertificatePEMMissing
	}

	 := CertificateFromX509(, )

	return &, nil
}

// PEM returns the certificate encoded as two pem block: once for the X509
// certificate and the other for the private key.
func ( Certificate) () (string, error) {
	// First write the X509 certificate
	var  strings.Builder
	 := make(
		[]byte, base64.StdEncoding.EncodedLen(len(.x509Cert.Raw)))
	base64.StdEncoding.Encode(, .x509Cert.Raw)
	 := pem.Encode(&, &pem.Block{Type: "CERTIFICATE", Bytes: })
	if  != nil {
		return "", fmt.Errorf("failed to pem encode the X certificate: %w", )
	}
	// Next write the private key
	,  := x509.MarshalPKCS8PrivateKey(.privateKey)
	if  != nil {
		return "", fmt.Errorf("failed to marshal private key: %w", )
	}
	 = pem.Encode(&, &pem.Block{Type: "PRIVATE KEY", Bytes: })
	if  != nil {
		return "", fmt.Errorf("failed to encode private key: %w", )
	}

	return .String(), nil
}