diff --git a/msg.go b/msg.go index 758373b..6ca88ee 100644 --- a/msg.go +++ b/msg.go @@ -367,23 +367,45 @@ func (m *Msg) SignWithTLSCertificate(keyPairTlS *tls.Certificate) error { return fmt.Errorf("failed to parse intermediate certificate: %w", err) } + leafCertificate, err := getLeafCertificate(keyPairTlS) + if err != nil { + return fmt.Errorf("failed to get leaf certificate: %w", err) + } + switch keyPairTlS.PrivateKey.(type) { case *rsa.PrivateKey: if intermediateCertificate == nil { - return m.SignWithSMimeRSA(keyPairTlS.PrivateKey.(*rsa.PrivateKey), keyPairTlS.Leaf, nil) + return m.SignWithSMimeRSA(keyPairTlS.PrivateKey.(*rsa.PrivateKey), leafCertificate, nil) } - return m.SignWithSMimeRSA(keyPairTlS.PrivateKey.(*rsa.PrivateKey), keyPairTlS.Leaf, intermediateCertificate) + return m.SignWithSMimeRSA(keyPairTlS.PrivateKey.(*rsa.PrivateKey), leafCertificate, intermediateCertificate) case *ecdsa.PrivateKey: if intermediateCertificate == nil { - return m.SignWithSMimeECDSA(keyPairTlS.PrivateKey.(*ecdsa.PrivateKey), keyPairTlS.Leaf, nil) + return m.SignWithSMimeECDSA(keyPairTlS.PrivateKey.(*ecdsa.PrivateKey), leafCertificate, nil) } - return m.SignWithSMimeECDSA(keyPairTlS.PrivateKey.(*ecdsa.PrivateKey), keyPairTlS.Leaf, intermediateCertificate) + return m.SignWithSMimeECDSA(keyPairTlS.PrivateKey.(*ecdsa.PrivateKey), leafCertificate, intermediateCertificate) default: return fmt.Errorf("unsupported private key type: %T", keyPairTlS.PrivateKey) } } +// getLeafCertificate returns the leaf certificate from a tls.Certificate. +// PLEASE NOTE: Before Go 1.23 Certificate.Leaf was left nil, and the parsed certificate was +// discarded. This behavior can be re-enabled by setting "x509keypairleaf=0" +// in the GODEBUG environment variable. +func getLeafCertificate(keyPairTlS *tls.Certificate) (*x509.Certificate, error) { + if keyPairTlS.Leaf != nil { + return keyPairTlS.Leaf, nil + } + + cert, err := x509.ParseCertificate(keyPairTlS.Certificate[0]) + if err != nil { + return nil, err + } + + return cert, nil +} + // This method allows you to specify a character set for the email message. The charset is // important for ensuring that the content of the message is correctly interpreted by // mail clients. Common charset values include UTF-8, ISO-8859-1, and others. If a charset diff --git a/msg_test.go b/msg_test.go index aa46f94..d984178 100644 --- a/msg_test.go +++ b/msg_test.go @@ -3379,6 +3379,47 @@ func TestSignWithSMime_ValidECDSAKeyPair(t *testing.T) { } } +// TestSignWithTLSCertificate tests SignWithTLSCertificate with given *tls.Certificate +func TestSignWithTLSCertificate(t *testing.T) { + keyPairTLS, err := getDummyKeyPairTLS() + if err != nil { + t.Errorf("failed to laod dummy crypto material. Cause: %v", err) + } + m := NewMsg() + if err := m.SignWithTLSCertificate(keyPairTLS); err != nil { + t.Errorf("failed to set sMime. Cause: %v", err) + } + if m.sMime.privateKey.ecdsa == nil { + t.Errorf("SignWithTLSCertificate() - no private key is given") + } + if m.sMime.certificate == nil { + t.Errorf("SignWithTLSCertificate() - no certificate is given") + } +} + +// TestSignWithTLSCertificate tests SignWithTLSCertificate with given *tls.Certificate and nil leaf certificate +// PLEASE NOTE: Before Go 1.23 Certificate.Leaf was left nil, and the parsed certificate was +// discarded. This behavior can be re-enabled by setting "x509keypairleaf=0" +// in the GODEBUG environment variable. +func TestSignWithTLSCertificate_WithKeyPairLeafNil(t *testing.T) { + t.Setenv("GODEBUG", "x509keypairleaf=0") + + keyPairTLS, err := getDummyKeyPairTLS() + if err != nil { + t.Errorf("failed to laod dummy crypto material. Cause: %v", err) + } + m := NewMsg() + if err := m.SignWithTLSCertificate(keyPairTLS); err != nil { + t.Errorf("failed to set sMime. Cause: %v", err) + } + if m.sMime.privateKey.ecdsa == nil { + t.Errorf("SignWithTLSCertificate() - no private key is given") + } + if m.sMime.certificate == nil { + t.Errorf("SignWithTLSCertificate() - no certificate is given") + } +} + // TestSignWithSMime_InvalidPrivateKey tests WithSMimeSinging with given invalid private key func TestSignWithSMime_InvalidPrivateKey(t *testing.T) { m := NewMsg() @@ -3511,3 +3552,19 @@ func TestMsg_signMessage(t *testing.T) { t.Errorf("createSignaturePart() method failed. Expected content should not be equal: %s, got: %s", body, signaturePart.GetEncoding()) } } + +// TestGetLeafCertificate tests the Msg.getLeafCertificate method +func TestGetLeafCertificate(t *testing.T) { + keyPairTLS, err := getDummyKeyPairTLS() + if err != nil { + t.Errorf("failed to laod dummy crypto material. Cause: %v", err) + } + + leafCertificate, err := getLeafCertificate(keyPairTLS) + if err != nil { + t.Errorf("failed to get leaf certificate. Cause: %v", err) + } + if leafCertificate == nil { + t.Errorf("failed to get leaf certificate") + } +} diff --git a/util_test.go b/util_test.go index 7640f62..8a6c6bc 100644 --- a/util_test.go +++ b/util_test.go @@ -19,7 +19,7 @@ const ( keyECDSAFilePath = "dummy-child-key-ecdsa.pem" ) -// getDummyRSACryptoMaterial loads a certificate (RSA) and the associated private key (ECDSA) form local disk for testing purposes +// getDummyRSACryptoMaterial loads a certificate (RSA), the associated private key and certificate (RSA) is loaded from local disk for testing purposes func getDummyRSACryptoMaterial() (*rsa.PrivateKey, *x509.Certificate, *x509.Certificate, error) { keyPair, err := tls.LoadX509KeyPair(certRSAFilePath, keyRSAFilePath) if err != nil { @@ -41,7 +41,7 @@ func getDummyRSACryptoMaterial() (*rsa.PrivateKey, *x509.Certificate, *x509.Cert return privateKey, certificate, intermediateCertificate, nil } -// getDummyECDSACryptoMaterial loads a certificate (ECDSA) and the associated private key (ECDSA) form local disk for testing purposes +// getDummyECDSACryptoMaterial loads a certificate (ECDSA), the associated private key and certificate (ECDSA) is loaded from local disk for testing purposes func getDummyECDSACryptoMaterial() (*ecdsa.PrivateKey, *x509.Certificate, *x509.Certificate, error) { keyPair, err := tls.LoadX509KeyPair(certECDSAFilePath, keyECDSAFilePath) if err != nil { @@ -62,3 +62,12 @@ func getDummyECDSACryptoMaterial() (*ecdsa.PrivateKey, *x509.Certificate, *x509. return privateKey, certificate, intermediateCertificate, nil } + +// getDummyKeyPairTLS loads a certificate (ECDSA) as *tls.Certificate, the associated private key and certificate (ECDSA) is loaded from local disk for testing purposes +func getDummyKeyPairTLS() (*tls.Certificate, error) { + keyPair, err := tls.LoadX509KeyPair(certECDSAFilePath, keyECDSAFilePath) + if err != nil { + return nil, err + } + return &keyPair, err +}