mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-22 22:00:49 +01:00
Compare commits
5 commits
128b2bd32e
...
cc4c5bfd04
Author | SHA1 | Date | |
---|---|---|---|
|
cc4c5bfd04 | ||
|
4b21cc617b | ||
|
4a3dbddaff | ||
|
978a01eaf6 | ||
|
efd8aa93b6 |
18 changed files with 768 additions and 89 deletions
26
dummy-chain-cert-ecdsa.pem
Normal file
26
dummy-chain-cert-ecdsa.pem
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICAzCCAaqgAwIBAgIUKNWGvPrlzuYHnP4m6nGe60LalYEwCgYIKoZIzj0EAwIw
|
||||||
|
ZTELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRcw
|
||||||
|
FQYDVQQKDA5JbnRlcm1lZGlhdGVDQTEeMBwGA1UEAwwVSW50ZXJtZWRpYXRlIEVD
|
||||||
|
RFNBIENBMB4XDTI0MTAxNzEzMDg0N1oXDTI2MTAxNzEzMDg0N1owWzELMAkGA1UE
|
||||||
|
BhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MRIwEAYDVQQKDAlF
|
||||||
|
bmRFbnRpdHkxGTAXBgNVBAMMEEVuZCBFbnRpdHkgRUNEU0EwWTATBgcqhkjOPQIB
|
||||||
|
BggqhkjOPQMBBwNCAATMkQg2RhaDYOWs+Ik/NKgZdwxm4BzagwVou6R73uiwXsRi
|
||||||
|
QgJfhBxp01S3JI4E8AiboFf4uPRnRh0HGl9cUR+po0IwQDAdBgNVHQ4EFgQUz247
|
||||||
|
eAeMuM2We19rA5HnLzyLZEUwHwYDVR0jBBgwFoAU6oYLh690kT1bIB3DUA/SGRim
|
||||||
|
dXswCgYIKoZIzj0EAwIDRwAwRAIgI7cIpGzoiU1IoTYniEGXtK+WXq4Luv8k3BJQ
|
||||||
|
W16RAVsCICnuLaRyH/5nA3mmciAiF5R9PKDzyBnJcPGuCM1tmEpN
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIB/zCCAaSgAwIBAgIURGCUhG09dwZ2DwtWHjQxGTnNT3UwCgYIKoZIzj0EAwIw
|
||||||
|
VTELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5MQ8w
|
||||||
|
DQYDVQQKDAZSb290Q0ExFjAUBgNVBAMMDVJvb3QgRUNEU0EgQ0EwHhcNMjQxMDE3
|
||||||
|
MTMwODMwWhcNMjkxMDE2MTMwODMwWjBlMQswCQYDVQQGEwJVUzEOMAwGA1UECAwF
|
||||||
|
U3RhdGUxDTALBgNVBAcMBENpdHkxFzAVBgNVBAoMDkludGVybWVkaWF0ZUNBMR4w
|
||||||
|
HAYDVQQDDBVJbnRlcm1lZGlhdGUgRUNEU0EgQ0EwWTATBgcqhkjOPQIBBggqhkjO
|
||||||
|
PQMBBwNCAASYjGGvlMFZwnqUc8+jt9I7qRT8IP5gYLPZ7oiV/oaGNinmtzG7UXC/
|
||||||
|
2PEDvdqpMPNw65IaP0d8z+c5lUxneE70o0IwQDAdBgNVHQ4EFgQU6oYLh690kT1b
|
||||||
|
IB3DUA/SGRimdXswHwYDVR0jBBgwFoAUGVvIlDmd/tCejujJcy4xTvgkw3IwCgYI
|
||||||
|
KoZIzj0EAwIDSQAwRgIhAO0rw5JvMu5y8JO958/4FThdjwOsg/IDGryQ3QQM0tw1
|
||||||
|
AiEA07W1o81WIOVLZzwyTJAN8SnpSRIXgV/+ccst2T7s7Zs=
|
||||||
|
-----END CERTIFICATE-----
|
8
dummy-child-key-ecdsa.pem
Normal file
8
dummy-child-key-ecdsa.pem
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
-----BEGIN EC PARAMETERS-----
|
||||||
|
BggqhkjOPQMBBw==
|
||||||
|
-----END EC PARAMETERS-----
|
||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MHcCAQEEIG20X4lI7vocwK2W3TYOW+XtQv/bwRWYknvAf2OK8WFJoAoGCCqGSM49
|
||||||
|
AwEHoUQDQgAEzJEINkYWg2DlrPiJPzSoGXcMZuAc2oMFaLuke97osF7EYkICX4Qc
|
||||||
|
adNUtySOBPAIm6BX+Lj0Z0YdBxpfXFEfqQ==
|
||||||
|
-----END EC PRIVATE KEY-----
|
3
dummy-child-key-ecdsa.pem.license
Normal file
3
dummy-child-key-ecdsa.pem.license
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 The go-mail Authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
3
dummy-child-key-rsa.pem.license
Normal file
3
dummy-child-key-rsa.pem.license
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 The go-mail Authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
1
go.mod
1
go.mod
|
@ -7,7 +7,6 @@ module github.com/wneessen/go-mail
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
go.mozilla.org/pkcs7 v0.9.0
|
|
||||||
golang.org/x/crypto v0.28.0
|
golang.org/x/crypto v0.28.0
|
||||||
golang.org/x/text v0.19.0
|
golang.org/x/text v0.19.0
|
||||||
)
|
)
|
2
go.sum
2
go.sum
|
@ -20,8 +20,6 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI=
|
|
||||||
go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
|
42
msg.go
42
msg.go
|
@ -7,7 +7,9 @@ package mail
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"embed"
|
"embed"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -338,9 +340,9 @@ func WithNoDefaultUserAgent() MsgOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignWithSMime configures the Msg to be signed with S/MIME
|
// SignWithSMimeRSA configures the Msg to be signed with S/MIME
|
||||||
func (m *Msg) SignWithSMime(privateKey *rsa.PrivateKey, certificate *x509.Certificate, intermediateCertificate *x509.Certificate) error {
|
func (m *Msg) SignWithSMimeRSA(privateKey *rsa.PrivateKey, certificate *x509.Certificate, intermediateCertificate *x509.Certificate) error {
|
||||||
sMime, err := newSMime(privateKey, certificate, intermediateCertificate)
|
sMime, err := newSMimeWithRSA(privateKey, certificate, intermediateCertificate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -348,6 +350,40 @@ func (m *Msg) SignWithSMime(privateKey *rsa.PrivateKey, certificate *x509.Certif
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignWithSMimeECDSA configures the Msg to be signed with S/MIME
|
||||||
|
func (m *Msg) SignWithSMimeECDSA(privateKey *ecdsa.PrivateKey, certificate *x509.Certificate, intermediateCertificate *x509.Certificate) error {
|
||||||
|
sMime, err := newSMimeWithECDSA(privateKey, certificate, intermediateCertificate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.sMime = sMime
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignWithTLSCertificate signs the Msg with the provided *tls.certificate.
|
||||||
|
func (m *Msg) SignWithTLSCertificate(keyPairTlS *tls.Certificate) error {
|
||||||
|
intermediateCertificate, err := x509.ParseCertificate(keyPairTlS.Certificate[1])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse intermediate 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), keyPairTlS.Leaf, intermediateCertificate)
|
||||||
|
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
if intermediateCertificate == nil {
|
||||||
|
return m.SignWithSMimeECDSA(keyPairTlS.PrivateKey.(*ecdsa.PrivateKey), keyPairTlS.Leaf, nil)
|
||||||
|
}
|
||||||
|
return m.SignWithSMimeECDSA(keyPairTlS.PrivateKey.(*ecdsa.PrivateKey), keyPairTlS.Leaf, intermediateCertificate)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported private key type: %T", keyPairTlS.PrivateKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This method allows you to specify a character set for the email message. The charset is
|
// 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
|
// 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
|
// mail clients. Common charset values include UTF-8, ISO-8859-1, and others. If a charset
|
||||||
|
|
68
msg_test.go
68
msg_test.go
|
@ -1909,14 +1909,14 @@ func TestMsg_hasAlt(t *testing.T) {
|
||||||
|
|
||||||
// TestMsg_hasAlt tests the hasAlt() method of the Msg with active S/MIME
|
// TestMsg_hasAlt tests the hasAlt() method of the Msg with active S/MIME
|
||||||
func TestMsg_hasAltWithSMime(t *testing.T) {
|
func TestMsg_hasAltWithSMime(t *testing.T) {
|
||||||
privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial()
|
privateKey, certificate, intermediateCertificate, err := getDummyRSACryptoMaterial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
||||||
}
|
}
|
||||||
m := NewMsg()
|
m := NewMsg()
|
||||||
m.SetBodyString(TypeTextPlain, "Plain")
|
m.SetBodyString(TypeTextPlain, "Plain")
|
||||||
m.AddAlternativeString(TypeTextHTML, "<b>HTML</b>")
|
m.AddAlternativeString(TypeTextHTML, "<b>HTML</b>")
|
||||||
if err := m.SignWithSMime(privateKey, certificate, intermediateCertificate); err != nil {
|
if err := m.SignWithSMimeRSA(privateKey, certificate, intermediateCertificate); err != nil {
|
||||||
t.Errorf("failed to init smime configuration")
|
t.Errorf("failed to init smime configuration")
|
||||||
}
|
}
|
||||||
if m.hasAlt() {
|
if m.hasAlt() {
|
||||||
|
@ -1926,12 +1926,12 @@ func TestMsg_hasAltWithSMime(t *testing.T) {
|
||||||
|
|
||||||
// TestMsg_hasSMime tests the hasSMime() method of the Msg
|
// TestMsg_hasSMime tests the hasSMime() method of the Msg
|
||||||
func TestMsg_hasSMime(t *testing.T) {
|
func TestMsg_hasSMime(t *testing.T) {
|
||||||
privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial()
|
privateKey, certificate, intermediateCertificate, err := getDummyRSACryptoMaterial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
||||||
}
|
}
|
||||||
m := NewMsg()
|
m := NewMsg()
|
||||||
if err := m.SignWithSMime(privateKey, certificate, intermediateCertificate); err != nil {
|
if err := m.SignWithSMimeRSA(privateKey, certificate, intermediateCertificate); err != nil {
|
||||||
t.Errorf("failed to init smime configuration")
|
t.Errorf("failed to init smime configuration")
|
||||||
}
|
}
|
||||||
m.SetBodyString(TypeTextPlain, "Plain")
|
m.SetBodyString(TypeTextPlain, "Plain")
|
||||||
|
@ -2009,7 +2009,7 @@ func TestMsg_WriteToSkipMiddleware(t *testing.T) {
|
||||||
|
|
||||||
// TestMsg_WriteToWithSMIME tests the WriteTo() method of the Msg
|
// TestMsg_WriteToWithSMIME tests the WriteTo() method of the Msg
|
||||||
func TestMsg_WriteToWithSMIME(t *testing.T) {
|
func TestMsg_WriteToWithSMIME(t *testing.T) {
|
||||||
privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial()
|
privateKey, certificate, intermediateCertificate, err := getDummyRSACryptoMaterial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -2017,7 +2017,7 @@ func TestMsg_WriteToWithSMIME(t *testing.T) {
|
||||||
m := NewMsg()
|
m := NewMsg()
|
||||||
m.Subject("This is a test")
|
m.Subject("This is a test")
|
||||||
m.SetBodyString(TypeTextPlain, "Plain")
|
m.SetBodyString(TypeTextPlain, "Plain")
|
||||||
if err := m.SignWithSMime(privateKey, certificate, intermediateCertificate); err != nil {
|
if err := m.SignWithSMimeRSA(privateKey, certificate, intermediateCertificate); err != nil {
|
||||||
t.Errorf("failed to init smime configuration")
|
t.Errorf("failed to init smime configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3343,17 +3343,35 @@ func TestNewMsgWithNoDefaultUserAgent(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSignWithSMime_ValidKeyPair tests WithSMimeSinging with given key pair
|
// TestSignWithSMime_ValidRSAKeyPair tests WithSMimeSinging with given rsa key pair
|
||||||
func TestSignWithSMime_ValidKeyPair(t *testing.T) {
|
func TestSignWithSMime_ValidRSAKeyPair(t *testing.T) {
|
||||||
privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial()
|
privateKey, certificate, intermediateCertificate, err := getDummyRSACryptoMaterial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
||||||
}
|
}
|
||||||
m := NewMsg()
|
m := NewMsg()
|
||||||
if err := m.SignWithSMime(privateKey, certificate, intermediateCertificate); err != nil {
|
if err := m.SignWithSMimeRSA(privateKey, certificate, intermediateCertificate); err != nil {
|
||||||
t.Errorf("failed to set sMime. Cause: %v", err)
|
t.Errorf("failed to set sMime. Cause: %v", err)
|
||||||
}
|
}
|
||||||
if m.sMime.privateKey == nil {
|
if m.sMime.privateKey.rsa == nil {
|
||||||
|
t.Errorf("WithSMimeSinging() - no private key is given")
|
||||||
|
}
|
||||||
|
if m.sMime.certificate == nil {
|
||||||
|
t.Errorf("WithSMimeSinging() - no certificate is given")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSignWithSMime_ValidRSAKeyPair tests WithSMimeSinging with given ecdsa key pair
|
||||||
|
func TestSignWithSMime_ValidECDSAKeyPair(t *testing.T) {
|
||||||
|
privateKey, certificate, intermediateCertificate, err := getDummyECDSACryptoMaterial()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
||||||
|
}
|
||||||
|
m := NewMsg()
|
||||||
|
if err := m.SignWithSMimeECDSA(privateKey, certificate, intermediateCertificate); err != nil {
|
||||||
|
t.Errorf("failed to set sMime. Cause: %v", err)
|
||||||
|
}
|
||||||
|
if m.sMime.privateKey.ecdsa == nil {
|
||||||
t.Errorf("WithSMimeSinging() - no private key is given")
|
t.Errorf("WithSMimeSinging() - no private key is given")
|
||||||
}
|
}
|
||||||
if m.sMime.certificate == nil {
|
if m.sMime.certificate == nil {
|
||||||
|
@ -3365,7 +3383,7 @@ func TestSignWithSMime_ValidKeyPair(t *testing.T) {
|
||||||
func TestSignWithSMime_InvalidPrivateKey(t *testing.T) {
|
func TestSignWithSMime_InvalidPrivateKey(t *testing.T) {
|
||||||
m := NewMsg()
|
m := NewMsg()
|
||||||
|
|
||||||
err := m.SignWithSMime(nil, nil, nil)
|
err := m.SignWithSMimeRSA(nil, nil, nil)
|
||||||
if !errors.Is(err, ErrInvalidPrivateKey) {
|
if !errors.Is(err, ErrInvalidPrivateKey) {
|
||||||
t.Errorf("failed to pre-check SignWithSMime method values correctly: %s", err)
|
t.Errorf("failed to pre-check SignWithSMime method values correctly: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -3373,32 +3391,18 @@ func TestSignWithSMime_InvalidPrivateKey(t *testing.T) {
|
||||||
|
|
||||||
// TestSignWithSMime_InvalidCertificate tests WithSMimeSinging with given invalid certificate
|
// TestSignWithSMime_InvalidCertificate tests WithSMimeSinging with given invalid certificate
|
||||||
func TestSignWithSMime_InvalidCertificate(t *testing.T) {
|
func TestSignWithSMime_InvalidCertificate(t *testing.T) {
|
||||||
privateKey, _, _, err := getDummyCryptoMaterial()
|
privateKey, _, _, err := getDummyRSACryptoMaterial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
||||||
}
|
}
|
||||||
m := NewMsg()
|
m := NewMsg()
|
||||||
|
|
||||||
err = m.SignWithSMime(privateKey, nil, nil)
|
err = m.SignWithSMimeRSA(privateKey, nil, nil)
|
||||||
if !errors.Is(err, ErrInvalidCertificate) {
|
if !errors.Is(err, ErrInvalidCertificate) {
|
||||||
t.Errorf("failed to pre-check SignWithSMime method values correctly: %s", err)
|
t.Errorf("failed to pre-check SignWithSMime method values correctly: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSignWithSMime_InvalidIntermediateCertificate tests WithSMimeSinging with given invalid intermediate certificate
|
|
||||||
func TestSignWithSMime_InvalidIntermediateCertificate(t *testing.T) {
|
|
||||||
privateKey, certificate, _, err := getDummyCryptoMaterial()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
|
||||||
}
|
|
||||||
m := NewMsg()
|
|
||||||
|
|
||||||
err = m.SignWithSMime(privateKey, certificate, nil)
|
|
||||||
if !errors.Is(err, ErrInvalidIntermediateCertificate) {
|
|
||||||
t.Errorf("failed to pre-check SignWithSMime method values correctly: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fuzzing tests
|
// Fuzzing tests
|
||||||
func FuzzMsg_Subject(f *testing.F) {
|
func FuzzMsg_Subject(f *testing.F) {
|
||||||
f.Add("Testsubject")
|
f.Add("Testsubject")
|
||||||
|
@ -3429,12 +3433,12 @@ func FuzzMsg_From(f *testing.F) {
|
||||||
|
|
||||||
// TestMsg_createSignaturePart tests the Msg.createSignaturePart method
|
// TestMsg_createSignaturePart tests the Msg.createSignaturePart method
|
||||||
func TestMsg_createSignaturePart(t *testing.T) {
|
func TestMsg_createSignaturePart(t *testing.T) {
|
||||||
privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial()
|
privateKey, certificate, intermediateCertificate, err := getDummyRSACryptoMaterial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
||||||
}
|
}
|
||||||
m := NewMsg()
|
m := NewMsg()
|
||||||
if err := m.SignWithSMime(privateKey, certificate, intermediateCertificate); err != nil {
|
if err := m.SignWithSMimeRSA(privateKey, certificate, intermediateCertificate); err != nil {
|
||||||
t.Errorf("failed to init smime configuration")
|
t.Errorf("failed to init smime configuration")
|
||||||
}
|
}
|
||||||
body := []byte("This is the body")
|
body := []byte("This is the body")
|
||||||
|
@ -3459,14 +3463,14 @@ func TestMsg_createSignaturePart(t *testing.T) {
|
||||||
|
|
||||||
// TestMsg_signMessage tests the Msg.signMessage method
|
// TestMsg_signMessage tests the Msg.signMessage method
|
||||||
func TestMsg_signMessage(t *testing.T) {
|
func TestMsg_signMessage(t *testing.T) {
|
||||||
privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial()
|
privateKey, certificate, intermediateCertificate, err := getDummyRSACryptoMaterial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
||||||
}
|
}
|
||||||
body := []byte("This is the body")
|
body := []byte("This is the body")
|
||||||
m := NewMsg()
|
m := NewMsg()
|
||||||
m.SetBodyString(TypeTextPlain, string(body))
|
m.SetBodyString(TypeTextPlain, string(body))
|
||||||
if err := m.SignWithSMime(privateKey, certificate, intermediateCertificate); err != nil {
|
if err := m.SignWithSMimeRSA(privateKey, certificate, intermediateCertificate); err != nil {
|
||||||
t.Errorf("failed to init smime configuration")
|
t.Errorf("failed to init smime configuration")
|
||||||
}
|
}
|
||||||
msg, err := m.signMessage(m)
|
msg, err := m.signMessage(m)
|
||||||
|
|
|
@ -157,13 +157,13 @@ func TestMsgWriter_writeMsg_PGP(t *testing.T) {
|
||||||
|
|
||||||
// TestMsgWriter_writeMsg_SMime tests the writeMsg method of the msgWriter with S/MIME types set
|
// TestMsgWriter_writeMsg_SMime tests the writeMsg method of the msgWriter with S/MIME types set
|
||||||
func TestMsgWriter_writeMsg_SMime(t *testing.T) {
|
func TestMsgWriter_writeMsg_SMime(t *testing.T) {
|
||||||
privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial()
|
privateKey, certificate, intermediateCertificate, err := getDummyRSACryptoMaterial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
t.Errorf("failed to laod dummy crypto material. Cause: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
m := NewMsg()
|
m := NewMsg()
|
||||||
if err := m.SignWithSMime(privateKey, certificate, intermediateCertificate); err != nil {
|
if err := m.SignWithSMimeRSA(privateKey, certificate, intermediateCertificate); err != nil {
|
||||||
t.Errorf("failed to init smime configuration")
|
t.Errorf("failed to init smime configuration")
|
||||||
}
|
}
|
||||||
_ = m.From(`"Toni Tester" <test@example.com>`)
|
_ = m.From(`"Toni Tester" <test@example.com>`)
|
||||||
|
|
388
pkcs7.go
Normal file
388
pkcs7.go
Normal file
|
@ -0,0 +1,388 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022-2023 The go-mail Authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package mail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "crypto/sha1" // for crypto.SHA1
|
||||||
|
)
|
||||||
|
|
||||||
|
// PKCS7 Represents a PKCS7 structure
|
||||||
|
type PKCS7 struct {
|
||||||
|
Content []byte
|
||||||
|
Certificates []*x509.Certificate
|
||||||
|
CRLs []x509.RevocationList
|
||||||
|
Signers []signerInfo
|
||||||
|
raw interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type contentInfo struct {
|
||||||
|
ContentType asn1.ObjectIdentifier
|
||||||
|
Content asn1.RawValue `asn1:"explicit,optional,tag:0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
oidData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1}
|
||||||
|
oidSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2}
|
||||||
|
oidAttributeContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3}
|
||||||
|
oidAttributeMessageDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4}
|
||||||
|
oidAttributeSigningTime = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 5}
|
||||||
|
)
|
||||||
|
|
||||||
|
type signedData struct {
|
||||||
|
Version int `asn1:"default:1"`
|
||||||
|
DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"`
|
||||||
|
ContentInfo contentInfo
|
||||||
|
Certificates rawCertificates `asn1:"optional,tag:0"`
|
||||||
|
CRLs []x509.RevocationList `asn1:"optional,tag:1"`
|
||||||
|
SignerInfos []signerInfo `asn1:"set"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type rawCertificates struct {
|
||||||
|
Raw asn1.RawContent
|
||||||
|
}
|
||||||
|
|
||||||
|
type attribute struct {
|
||||||
|
Type asn1.ObjectIdentifier
|
||||||
|
Value asn1.RawValue `asn1:"set"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type issuerAndSerial struct {
|
||||||
|
IssuerName asn1.RawValue
|
||||||
|
SerialNumber *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageDigestMismatchError is returned when the signer data digest does not
|
||||||
|
// match the computed digest for the contained content
|
||||||
|
type MessageDigestMismatchError struct {
|
||||||
|
ExpectedDigest []byte
|
||||||
|
ActualDigest []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *MessageDigestMismatchError) Error() string {
|
||||||
|
return fmt.Sprintf("pkcs7: Message digest mismatch\n\tExpected: %X\n\tActual : %X", err.ExpectedDigest, err.ActualDigest)
|
||||||
|
}
|
||||||
|
|
||||||
|
type signerInfo struct {
|
||||||
|
Version int `asn1:"default:1"`
|
||||||
|
IssuerAndSerialNumber issuerAndSerial
|
||||||
|
DigestAlgorithm pkix.AlgorithmIdentifier
|
||||||
|
AuthenticatedAttributes []attribute `asn1:"optional,tag:0"`
|
||||||
|
DigestEncryptionAlgorithm pkix.AlgorithmIdentifier
|
||||||
|
EncryptedDigest []byte
|
||||||
|
UnauthenticatedAttributes []attribute `asn1:"optional,tag:1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (raw rawCertificates) Parse() ([]*x509.Certificate, error) {
|
||||||
|
if len(raw.Raw) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var val asn1.RawValue
|
||||||
|
if _, err := asn1.Unmarshal(raw.Raw, &val); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return x509.ParseCertificates(val.Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalAttributes(attrs []attribute) ([]byte, error) {
|
||||||
|
encodedAttributes, err := asn1.Marshal(struct {
|
||||||
|
A []attribute `asn1:"set"`
|
||||||
|
}{A: attrs})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the leading sequence octets
|
||||||
|
var raw asn1.RawValue
|
||||||
|
if _, err := asn1.Unmarshal(encodedAttributes, &raw); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return raw.Bytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
oidDigestAlgorithmSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26}
|
||||||
|
oidSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5}
|
||||||
|
)
|
||||||
|
|
||||||
|
func getCertFromCertsByIssuerAndSerial(certs []*x509.Certificate, ias issuerAndSerial) *x509.Certificate {
|
||||||
|
for _, cert := range certs {
|
||||||
|
if isCertMatchForIssuerAndSerial(cert, ias) {
|
||||||
|
return cert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOnlySigner returns an x509.Certificate for the first signer of the signed
|
||||||
|
// data payload. If there are more or less than one signer, nil is returned
|
||||||
|
func (p7 *PKCS7) GetOnlySigner() *x509.Certificate {
|
||||||
|
if len(p7.Signers) != 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
signer := p7.Signers[0]
|
||||||
|
return getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUnsupportedAlgorithm tells you when our quick dev assumptions have failed
|
||||||
|
var ErrUnsupportedAlgorithm = errors.New("pkcs7: cannot decrypt data: only RSA, DES, DES-EDE3, AES-256-CBC and AES-128-GCM supported")
|
||||||
|
|
||||||
|
func isCertMatchForIssuerAndSerial(cert *x509.Certificate, ias issuerAndSerial) bool {
|
||||||
|
return cert.SerialNumber.Cmp(ias.SerialNumber) == 0 && bytes.Equal(cert.RawIssuer, ias.IssuerName.FullBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalAttribute(attrs []attribute, attributeType asn1.ObjectIdentifier, out interface{}) error {
|
||||||
|
for _, attr := range attrs {
|
||||||
|
if attr.Type.Equal(attributeType) {
|
||||||
|
_, err := asn1.Unmarshal(attr.Value.Bytes, out)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("pkcs7: attribute type not in attributes")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalSignedAttribute decodes a single attribute from the signer info
|
||||||
|
func (p7 *PKCS7) UnmarshalSignedAttribute(attributeType asn1.ObjectIdentifier, out interface{}) error {
|
||||||
|
sd, ok := p7.raw.(signedData)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("pkcs7: payload is not signedData content")
|
||||||
|
}
|
||||||
|
if len(sd.SignerInfos) < 1 {
|
||||||
|
return errors.New("pkcs7: payload has no signers")
|
||||||
|
}
|
||||||
|
attributes := sd.SignerInfos[0].AuthenticatedAttributes
|
||||||
|
return unmarshalAttribute(attributes, attributeType, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedData is an opaque data structure for creating signed data payloads
|
||||||
|
type SignedData struct {
|
||||||
|
sd signedData
|
||||||
|
certs []*x509.Certificate
|
||||||
|
messageDigest []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attribute represents a key value pair attribute. Value must be marshalable byte
|
||||||
|
// `encoding/asn1`
|
||||||
|
type Attribute struct {
|
||||||
|
Type asn1.ObjectIdentifier
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignerInfoConfig are optional values to include when adding a signer
|
||||||
|
type SignerInfoConfig struct {
|
||||||
|
ExtraSignedAttributes []Attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSignedData initializes a SignedData with content
|
||||||
|
func newSignedData(data []byte) (*SignedData, error) {
|
||||||
|
content, err := asn1.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ci := contentInfo{
|
||||||
|
ContentType: oidData,
|
||||||
|
Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true},
|
||||||
|
}
|
||||||
|
digAlg := pkix.AlgorithmIdentifier{
|
||||||
|
Algorithm: oidDigestAlgorithmSHA1,
|
||||||
|
}
|
||||||
|
h := crypto.SHA1.New()
|
||||||
|
h.Write(data)
|
||||||
|
md := h.Sum(nil)
|
||||||
|
sd := signedData{
|
||||||
|
ContentInfo: ci,
|
||||||
|
Version: 1,
|
||||||
|
DigestAlgorithmIdentifiers: []pkix.AlgorithmIdentifier{digAlg},
|
||||||
|
}
|
||||||
|
return &SignedData{sd: sd, messageDigest: md}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type attributes struct {
|
||||||
|
types []asn1.ObjectIdentifier
|
||||||
|
values []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the attribute, maintaining insertion order
|
||||||
|
func (attrs *attributes) Add(attrType asn1.ObjectIdentifier, value interface{}) {
|
||||||
|
attrs.types = append(attrs.types, attrType)
|
||||||
|
attrs.values = append(attrs.values, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortableAttribute struct {
|
||||||
|
SortKey []byte
|
||||||
|
Attribute attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
type attributeSet []sortableAttribute
|
||||||
|
|
||||||
|
func (sa attributeSet) Len() int {
|
||||||
|
return len(sa)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sa attributeSet) Less(i, j int) bool {
|
||||||
|
return bytes.Compare(sa[i].SortKey, sa[j].SortKey) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sa attributeSet) Swap(i, j int) {
|
||||||
|
sa[i], sa[j] = sa[j], sa[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sa attributeSet) attributes() []attribute {
|
||||||
|
attrs := make([]attribute, len(sa))
|
||||||
|
for i, attr := range sa {
|
||||||
|
attrs[i] = attr.Attribute
|
||||||
|
}
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (attrs *attributes) forMarshaling() ([]attribute, error) {
|
||||||
|
sortables := make(attributeSet, len(attrs.types))
|
||||||
|
for i := range sortables {
|
||||||
|
attrType := attrs.types[i]
|
||||||
|
attrValue := attrs.values[i]
|
||||||
|
asn1Value, err := asn1.Marshal(attrValue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
attr := attribute{
|
||||||
|
Type: attrType,
|
||||||
|
Value: asn1.RawValue{Tag: 17, IsCompound: true, Bytes: asn1Value}, // 17 == SET tag
|
||||||
|
}
|
||||||
|
encoded, err := asn1.Marshal(attr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sortables[i] = sortableAttribute{
|
||||||
|
SortKey: encoded,
|
||||||
|
Attribute: attr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Sort(sortables)
|
||||||
|
return sortables.attributes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addSigner signs attributes about the content and adds certificate to payload
|
||||||
|
func (sd *SignedData) addSigner(cert *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error {
|
||||||
|
attrs := &attributes{}
|
||||||
|
attrs.Add(oidAttributeContentType, sd.sd.ContentInfo.ContentType)
|
||||||
|
attrs.Add(oidAttributeMessageDigest, sd.messageDigest)
|
||||||
|
attrs.Add(oidAttributeSigningTime, time.Now())
|
||||||
|
for _, attr := range config.ExtraSignedAttributes {
|
||||||
|
attrs.Add(attr.Type, attr.Value)
|
||||||
|
}
|
||||||
|
finalAttrs, err := attrs.forMarshaling()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
signature, err := signAttributes(finalAttrs, pkey, crypto.SHA1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ias, err := cert2issuerAndSerial(cert)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
signer := signerInfo{
|
||||||
|
AuthenticatedAttributes: finalAttrs,
|
||||||
|
DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidDigestAlgorithmSHA1},
|
||||||
|
DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidSignatureSHA1WithRSA},
|
||||||
|
IssuerAndSerialNumber: ias,
|
||||||
|
EncryptedDigest: signature,
|
||||||
|
Version: 1,
|
||||||
|
}
|
||||||
|
// create signature of signed attributes
|
||||||
|
sd.certs = append(sd.certs, cert)
|
||||||
|
sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addCertificate adds the certificate to the payload. Useful for parent certificates
|
||||||
|
func (sd *SignedData) addCertificate(cert *x509.Certificate) {
|
||||||
|
sd.certs = append(sd.certs, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
// detach removes content from the signed data struct to make it a detached signature.
|
||||||
|
// This must be called right before Finish()
|
||||||
|
func (sd *SignedData) detach() {
|
||||||
|
sd.sd.ContentInfo = contentInfo{ContentType: oidData}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finish marshals the content and its signers
|
||||||
|
func (sd *SignedData) finish() ([]byte, error) {
|
||||||
|
sd.sd.Certificates = marshalCertificates(sd.certs)
|
||||||
|
inner, err := asn1.Marshal(sd.sd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
outer := contentInfo{
|
||||||
|
ContentType: oidSignedData,
|
||||||
|
Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: inner, IsCompound: true},
|
||||||
|
}
|
||||||
|
return asn1.Marshal(outer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cert2issuerAndSerial(cert *x509.Certificate) (issuerAndSerial, error) {
|
||||||
|
var ias issuerAndSerial
|
||||||
|
// The issuer RDNSequence has to match exactly the sequence in the certificate
|
||||||
|
// We cannot use cert.Issuer.ToRDNSequence() here since it mangles the sequence
|
||||||
|
ias.IssuerName = asn1.RawValue{FullBytes: cert.RawIssuer}
|
||||||
|
ias.SerialNumber = cert.SerialNumber
|
||||||
|
|
||||||
|
return ias, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// signs the DER encoded form of the attributes with the private key
|
||||||
|
func signAttributes(attrs []attribute, pkey crypto.PrivateKey, hash crypto.Hash) ([]byte, error) {
|
||||||
|
attrBytes, err := marshalAttributes(attrs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
h := hash.New()
|
||||||
|
h.Write(attrBytes)
|
||||||
|
hashed := h.Sum(nil)
|
||||||
|
switch priv := pkey.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
return rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA1, hashed)
|
||||||
|
}
|
||||||
|
return nil, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
// concats and wraps the certificates in the RawValue structure
|
||||||
|
func marshalCertificates(certs []*x509.Certificate) rawCertificates {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for _, cert := range certs {
|
||||||
|
buf.Write(cert.Raw)
|
||||||
|
}
|
||||||
|
rawCerts, _ := marshalCertificateBytes(buf.Bytes())
|
||||||
|
return rawCerts
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even though, the tag & length are stripped out during marshalling the
|
||||||
|
// RawContent, we have to encode it into the RawContent. If its missing,
|
||||||
|
// then `asn1.Marshal()` will strip out the certificate wrapper instead.
|
||||||
|
func marshalCertificateBytes(certs []byte) (rawCertificates, error) {
|
||||||
|
val := asn1.RawValue{Bytes: certs, Class: 2, Tag: 0, IsCompound: true}
|
||||||
|
b, err := asn1.Marshal(val)
|
||||||
|
if err != nil {
|
||||||
|
return rawCertificates{}, err
|
||||||
|
}
|
||||||
|
return rawCertificates{Raw: b}, nil
|
||||||
|
}
|
123
pkcs7_test.go
Normal file
123
pkcs7_test.go
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022-2023 The go-mail Authors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package mail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestSign_E2E tests S/MIME singing as e2e
|
||||||
|
func TestSign_E2E(t *testing.T) {
|
||||||
|
cert, err := createTestCertificate()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
content := []byte("Hello World")
|
||||||
|
for _, testDetach := range []bool{false, true} {
|
||||||
|
toBeSigned, err := newSignedData(content)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Cannot initialize signed data: %s", err)
|
||||||
|
}
|
||||||
|
if err := toBeSigned.addSigner(cert.Certificate, cert.PrivateKey, SignerInfoConfig{}); err != nil {
|
||||||
|
t.Fatalf("Cannot add signer: %s", err)
|
||||||
|
}
|
||||||
|
if testDetach {
|
||||||
|
t.Log("Testing detached signature")
|
||||||
|
toBeSigned.detach()
|
||||||
|
} else {
|
||||||
|
t.Log("Testing attached signature")
|
||||||
|
}
|
||||||
|
signed, err := toBeSigned.finish()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Cannot finish signing data: %s", err)
|
||||||
|
}
|
||||||
|
if err := pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: signed}); err != nil {
|
||||||
|
t.Fatalf("Cannot write signed data: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type certKeyPair struct {
|
||||||
|
Certificate *x509.Certificate
|
||||||
|
PrivateKey *rsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestCertificate() (*certKeyPair, error) {
|
||||||
|
signer, err := createTestCertificateByIssuer("Eddard Stark", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fmt.Println("Created root cert")
|
||||||
|
if err := pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: signer.Certificate.Raw}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pair, err := createTestCertificateByIssuer("Jon Snow", signer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fmt.Println("Created signer cert")
|
||||||
|
if err := pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: pair.Certificate.Raw}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pair, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestCertificateByIssuer(name string, issuer *certKeyPair) (*certKeyPair, error) {
|
||||||
|
priv, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 32)
|
||||||
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := x509.Certificate{
|
||||||
|
SerialNumber: serialNumber,
|
||||||
|
SignatureAlgorithm: x509.SHA256WithRSA,
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: name,
|
||||||
|
Organization: []string{"Acme Co"},
|
||||||
|
},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().AddDate(1, 0, 0),
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection},
|
||||||
|
}
|
||||||
|
var issuerCert *x509.Certificate
|
||||||
|
var issuerKey crypto.PrivateKey
|
||||||
|
if issuer != nil {
|
||||||
|
issuerCert = issuer.Certificate
|
||||||
|
issuerKey = issuer.PrivateKey
|
||||||
|
} else {
|
||||||
|
template.IsCA = true
|
||||||
|
template.KeyUsage |= x509.KeyUsageCertSign
|
||||||
|
issuerCert = &template
|
||||||
|
issuerKey = priv
|
||||||
|
}
|
||||||
|
cert, err := x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.Public(), issuerKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
leaf, err := x509.ParseCertificate(cert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &certKeyPair{
|
||||||
|
Certificate: leaf,
|
||||||
|
PrivateKey: priv,
|
||||||
|
}, nil
|
||||||
|
}
|
86
smime.go
86
smime.go
|
@ -6,14 +6,14 @@ package mail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.mozilla.org/pkcs7"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -22,35 +22,34 @@ var (
|
||||||
|
|
||||||
// ErrInvalidCertificate should be used if the certificate is invalid
|
// ErrInvalidCertificate should be used if the certificate is invalid
|
||||||
ErrInvalidCertificate = errors.New("invalid certificate")
|
ErrInvalidCertificate = errors.New("invalid certificate")
|
||||||
|
|
||||||
// ErrInvalidIntermediateCertificate should be used if the intermediate certificate is invalid
|
|
||||||
ErrInvalidIntermediateCertificate = errors.New("invalid intermediate certificate")
|
|
||||||
|
|
||||||
// ErrCouldNotInitialize should be used if the signed data could not initialize
|
|
||||||
ErrCouldNotInitialize = errors.New("could not initialize signed data")
|
|
||||||
|
|
||||||
// ErrCouldNotAddSigner should be used if the signer could not be added
|
|
||||||
ErrCouldNotAddSigner = errors.New("could not add signer message")
|
|
||||||
|
|
||||||
// ErrCouldNotFinishSigning should be used if the signing could not be finished
|
|
||||||
ErrCouldNotFinishSigning = errors.New("could not finish signing")
|
|
||||||
|
|
||||||
// ErrCouldNoEncodeToPEM should be used if the signature could not be encoded to PEM
|
|
||||||
ErrCouldNoEncodeToPEM = errors.New("could not encode to PEM")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// privateKeyHolder is the representation of a private key
|
||||||
|
type privateKeyHolder struct {
|
||||||
|
ecdsa *ecdsa.PrivateKey
|
||||||
|
rsa *rsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// get returns the private key of the privateKeyHolder
|
||||||
|
func (p privateKeyHolder) get() crypto.PrivateKey {
|
||||||
|
if p.ecdsa != nil {
|
||||||
|
return p.ecdsa
|
||||||
|
}
|
||||||
|
return p.rsa
|
||||||
|
}
|
||||||
|
|
||||||
// SMime is used to sign messages with S/MIME
|
// SMime is used to sign messages with S/MIME
|
||||||
type SMime struct {
|
type SMime struct {
|
||||||
privateKey *rsa.PrivateKey
|
privateKey privateKeyHolder
|
||||||
certificate *x509.Certificate
|
certificate *x509.Certificate
|
||||||
intermediateCertificate *x509.Certificate
|
intermediateCertificate *x509.Certificate
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSMime construct a new instance of SMime with provided parameters
|
// newSMimeWithRSA construct a new instance of SMime with provided parameters
|
||||||
// privateKey as *rsa.PrivateKey
|
// privateKey as *rsa.PrivateKey
|
||||||
// certificate as *x509.Certificate
|
// certificate as *x509.Certificate
|
||||||
// intermediateCertificate as *x509.Certificate
|
// intermediateCertificate (optional) as *x509.Certificate
|
||||||
func newSMime(privateKey *rsa.PrivateKey, certificate *x509.Certificate, intermediateCertificate *x509.Certificate) (*SMime, error) {
|
func newSMimeWithRSA(privateKey *rsa.PrivateKey, certificate *x509.Certificate, intermediateCertificate *x509.Certificate) (*SMime, error) {
|
||||||
if privateKey == nil {
|
if privateKey == nil {
|
||||||
return nil, ErrInvalidPrivateKey
|
return nil, ErrInvalidPrivateKey
|
||||||
}
|
}
|
||||||
|
@ -59,12 +58,28 @@ func newSMime(privateKey *rsa.PrivateKey, certificate *x509.Certificate, interme
|
||||||
return nil, ErrInvalidCertificate
|
return nil, ErrInvalidCertificate
|
||||||
}
|
}
|
||||||
|
|
||||||
if intermediateCertificate == nil {
|
return &SMime{
|
||||||
return nil, ErrInvalidIntermediateCertificate
|
privateKey: privateKeyHolder{rsa: privateKey},
|
||||||
|
certificate: certificate,
|
||||||
|
intermediateCertificate: intermediateCertificate,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSMimeWithECDSA construct a new instance of SMime with provided parameters
|
||||||
|
// privateKey as *ecdsa.PrivateKey
|
||||||
|
// certificate as *x509.Certificate
|
||||||
|
// intermediateCertificate (optional) as *x509.Certificate
|
||||||
|
func newSMimeWithECDSA(privateKey *ecdsa.PrivateKey, certificate *x509.Certificate, intermediateCertificate *x509.Certificate) (*SMime, error) {
|
||||||
|
if privateKey == nil {
|
||||||
|
return nil, ErrInvalidPrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if certificate == nil {
|
||||||
|
return nil, ErrInvalidCertificate
|
||||||
}
|
}
|
||||||
|
|
||||||
return &SMime{
|
return &SMime{
|
||||||
privateKey: privateKey,
|
privateKey: privateKeyHolder{ecdsa: privateKey},
|
||||||
certificate: certificate,
|
certificate: certificate,
|
||||||
intermediateCertificate: intermediateCertificate,
|
intermediateCertificate: intermediateCertificate,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -75,26 +90,29 @@ func (sm *SMime) signMessage(message string) (*string, error) {
|
||||||
lines := parseLines([]byte(message))
|
lines := parseLines([]byte(message))
|
||||||
toBeSigned := lines.bytesFromLines([]byte("\r\n"))
|
toBeSigned := lines.bytesFromLines([]byte("\r\n"))
|
||||||
|
|
||||||
signedData, err := pkcs7.NewSignedData(toBeSigned)
|
signedData, err := newSignedData(toBeSigned)
|
||||||
signedData.SetDigestAlgorithm(pkcs7.OIDDigestAlgorithmSHA256)
|
if err != nil || signedData == nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("could not initialize signed data: %w", err)
|
||||||
return nil, ErrCouldNotInitialize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = signedData.AddSignerChain(sm.certificate, sm.privateKey, []*x509.Certificate{sm.intermediateCertificate}, pkcs7.SignerInfoConfig{}); err != nil {
|
if err = signedData.addSigner(sm.certificate, sm.privateKey.get(), SignerInfoConfig{}); err != nil {
|
||||||
return nil, ErrCouldNotAddSigner
|
return nil, fmt.Errorf("could not add signer message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
signedData.Detach()
|
if sm.intermediateCertificate != nil {
|
||||||
|
signedData.addCertificate(sm.intermediateCertificate)
|
||||||
|
}
|
||||||
|
|
||||||
signatureDER, err := signedData.Finish()
|
signedData.detach()
|
||||||
|
|
||||||
|
signatureDER, err := signedData.finish()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrCouldNotFinishSigning
|
return nil, fmt.Errorf("could not finish signing: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pemMsg, err := encodeToPEM(signatureDER)
|
pemMsg, err := encodeToPEM(signatureDER)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrCouldNoEncodeToPEM
|
return nil, fmt.Errorf("could not encode to PEM: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pemMsg, nil
|
return pemMsg, nil
|
||||||
|
|
|
@ -6,25 +6,72 @@ package mail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestNewSMime tests the newSMime method
|
func TestGet_RSA(t *testing.T) {
|
||||||
func TestNewSMime(t *testing.T) {
|
p := privateKeyHolder{
|
||||||
privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial()
|
ecdsa: nil,
|
||||||
|
rsa: &rsa.PrivateKey{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.get() == nil {
|
||||||
|
t.Errorf("get() did not return the correct private key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet_ECDSA(t *testing.T) {
|
||||||
|
p := privateKeyHolder{
|
||||||
|
ecdsa: &ecdsa.PrivateKey{},
|
||||||
|
rsa: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.get() == nil {
|
||||||
|
t.Errorf("get() did not return the correct private key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewSMimeWithRSA tests the newSMime method with RSA crypto material
|
||||||
|
func TestNewSMimeWithRSA(t *testing.T) {
|
||||||
|
privateKey, certificate, intermediateCertificate, err := getDummyRSACryptoMaterial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error getting dummy crypto material: %s", err)
|
t.Errorf("Error getting dummy crypto material: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sMime, err := newSMime(privateKey, certificate, intermediateCertificate)
|
sMime, err := newSMimeWithRSA(privateKey, certificate, intermediateCertificate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error creating new SMime from keyPair: %s", err)
|
t.Errorf("Error creating new SMime from keyPair: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sMime.privateKey != privateKey {
|
if sMime.privateKey.rsa != privateKey {
|
||||||
|
t.Errorf("NewSMime() did not return the same private key")
|
||||||
|
}
|
||||||
|
if sMime.certificate != certificate {
|
||||||
|
t.Errorf("NewSMime() did not return the same certificate")
|
||||||
|
}
|
||||||
|
if sMime.intermediateCertificate != intermediateCertificate {
|
||||||
|
t.Errorf("NewSMime() did not return the same intermedidate certificate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewSMimeWithECDSA tests the newSMime method with ECDSA crypto material
|
||||||
|
func TestNewSMimeWithECDSA(t *testing.T) {
|
||||||
|
privateKey, certificate, intermediateCertificate, err := getDummyECDSACryptoMaterial()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error getting dummy crypto material: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sMime, err := newSMimeWithECDSA(privateKey, certificate, intermediateCertificate)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error creating new SMime from keyPair: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sMime.privateKey.ecdsa != privateKey {
|
||||||
t.Errorf("NewSMime() did not return the same private key")
|
t.Errorf("NewSMime() did not return the same private key")
|
||||||
}
|
}
|
||||||
if sMime.certificate != certificate {
|
if sMime.certificate != certificate {
|
||||||
|
@ -37,12 +84,12 @@ func TestNewSMime(t *testing.T) {
|
||||||
|
|
||||||
// TestSign tests the sign method
|
// TestSign tests the sign method
|
||||||
func TestSign(t *testing.T) {
|
func TestSign(t *testing.T) {
|
||||||
privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial()
|
privateKey, certificate, intermediateCertificate, err := getDummyRSACryptoMaterial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error getting dummy crypto material: %s", err)
|
t.Errorf("Error getting dummy crypto material: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sMime, err := newSMime(privateKey, certificate, intermediateCertificate)
|
sMime, err := newSMimeWithRSA(privateKey, certificate, intermediateCertificate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error creating new SMime from keyPair: %s", err)
|
t.Errorf("Error creating new SMime from keyPair: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -60,12 +107,12 @@ func TestSign(t *testing.T) {
|
||||||
|
|
||||||
// TestPrepareMessage tests the createMessage method
|
// TestPrepareMessage tests the createMessage method
|
||||||
func TestPrepareMessage(t *testing.T) {
|
func TestPrepareMessage(t *testing.T) {
|
||||||
privateKey, certificate, intermediateCertificate, err := getDummyCryptoMaterial()
|
privateKey, certificate, intermediateCertificate, err := getDummyRSACryptoMaterial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error getting dummy crypto material: %s", err)
|
t.Errorf("Error getting dummy crypto material: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sMime, err := newSMime(privateKey, certificate, intermediateCertificate)
|
sMime, err := newSMimeWithRSA(privateKey, certificate, intermediateCertificate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error creating new SMime from keyPair: %s", err)
|
t.Errorf("Error creating new SMime from keyPair: %s", err)
|
||||||
}
|
}
|
||||||
|
|
36
util_test.go
36
util_test.go
|
@ -5,19 +5,23 @@
|
||||||
package mail
|
package mail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
certFilePath = "dummy-chain-cert.pem"
|
certRSAFilePath = "dummy-chain-cert-rsa.pem"
|
||||||
keyFilePath = "dummy-child-key.pem"
|
keyRSAFilePath = "dummy-child-key-rsa.pem"
|
||||||
|
|
||||||
|
certECDSAFilePath = "dummy-chain-cert-ecdsa.pem"
|
||||||
|
keyECDSAFilePath = "dummy-child-key-ecdsa.pem"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getDummyCryptoMaterial loads a certificate and a private key form local disk for testing purposes
|
// getDummyRSACryptoMaterial loads a certificate (RSA) and the associated private key (ECDSA) form local disk for testing purposes
|
||||||
func getDummyCryptoMaterial() (*rsa.PrivateKey, *x509.Certificate, *x509.Certificate, error) {
|
func getDummyRSACryptoMaterial() (*rsa.PrivateKey, *x509.Certificate, *x509.Certificate, error) {
|
||||||
keyPair, err := tls.LoadX509KeyPair(certFilePath, keyFilePath)
|
keyPair, err := tls.LoadX509KeyPair(certRSAFilePath, keyRSAFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -36,3 +40,25 @@ func getDummyCryptoMaterial() (*rsa.PrivateKey, *x509.Certificate, *x509.Certifi
|
||||||
|
|
||||||
return privateKey, certificate, intermediateCertificate, nil
|
return privateKey, certificate, intermediateCertificate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getDummyECDSACryptoMaterial loads a certificate (ECDSA) and the associated private key (ECDSA) form local disk for testing purposes
|
||||||
|
func getDummyECDSACryptoMaterial() (*ecdsa.PrivateKey, *x509.Certificate, *x509.Certificate, error) {
|
||||||
|
keyPair, err := tls.LoadX509KeyPair(certECDSAFilePath, keyECDSAFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey := keyPair.PrivateKey.(*ecdsa.PrivateKey)
|
||||||
|
|
||||||
|
certificate, err := x509.ParseCertificate(keyPair.Certificate[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
intermediateCertificate, err := x509.ParseCertificate(keyPair.Certificate[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return privateKey, certificate, intermediateCertificate, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue