-
-
Notifications
You must be signed in to change notification settings - Fork 54
/
smime.go
138 lines (122 loc) · 4.5 KB
/
smime.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// SPDX-FileCopyrightText: The go-mail Authors
//
// SPDX-License-Identifier: MIT
package mail
import (
"crypto"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"github.com/wneessen/go-mail/internal/pkcs7"
)
var (
// ErrPrivateKeyMissing should be used if private key is invalid
ErrPrivateKeyMissing = errors.New("private key is missing")
// ErrCertificateMissing should be used if the certificate is invalid
ErrCertificateMissing = errors.New("certificate is missing")
)
// SMIME represents the configuration and state for S/MIME signing.
//
// This struct encapsulates the private key, certificate, optional intermediate certificate, and
// a flag indicating whether a signing process is currently in progress.
//
// Fields:
// - privateKey: The private key used for signing (implements crypto.PrivateKey).
// - certificate: The x509 certificate associated with the private key.
// - intermediateCert: An optional x509 intermediate certificate for chain validation.
// - inProgress: A boolean flag indicating if a signing operation is currently active.
type SMIME struct {
privateKey crypto.PrivateKey
certificate *x509.Certificate
intermediateCert *x509.Certificate
inProgress bool
}
// newSMIME constructs a new instance of SMIME with the provided parameters.
//
// This function initializes an SMIME object with a private key, certificate, and an optional
// intermediate certificate.
//
// Parameters:
// - privateKey: The private key used for signing (must implement crypto.PrivateKey).
// - certificate: The x509 certificate associated with the private key.
// - intermediateCert: An optional x509 intermediate certificate for chain validation.
//
// Returns:
// - An SMIME instance configured with the provided parameters.
// - An error if the private key or certificate is missing.
func newSMIME(privateKey crypto.PrivateKey, certificate *x509.Certificate,
intermediateCertificate *x509.Certificate,
) (*SMIME, error) {
if privateKey == nil {
return nil, ErrPrivateKeyMissing
}
if certificate == nil {
return nil, ErrCertificateMissing
}
return &SMIME{
privateKey: privateKey,
certificate: certificate,
intermediateCert: intermediateCertificate,
}, nil
}
// signMessage signs the provided message with S/MIME and returns the signature in DER format.
//
// This function creates S/MIME signed data using the configured private key and certificate.
// It optionally includes an intermediate certificate for chain validation and detaches the signature.
//
// Parameters:
// - message: The byte slice representing the message to be signed.
//
// Returns:
// - A string containing the S/MIME signature in DER format.
// - An error if initializing signed data, adding the signer, or finishing the signature fails.
func (s *SMIME) signMessage(message []byte) (string, error) {
signedData, err := pkcs7.NewSignedData(message)
if err != nil || signedData == nil {
return "", fmt.Errorf("failed to initialize signed data: %w", err)
}
if err = signedData.AddSigner(s.certificate, s.privateKey, pkcs7.SignerInfoConfig{}); err != nil {
return "", fmt.Errorf("could not add signer message: %w", err)
}
if s.intermediateCert != nil {
signedData.AddCertificate(s.intermediateCert)
}
signedData.Detach()
signatureDER, err := signedData.Finish()
if err != nil {
return "", fmt.Errorf("failed to finish signature: %w", err)
}
return string(signatureDER), nil
}
// getLeafCertificate retrieves the leaf certificate from a tls.Certificate.
//
// This function returns the parsed leaf certificate from the provided TLS certificate. If the Leaf field
// is nil, it parses and returns the first certificate in the chain.
//
// PLEASE NOTE: In Go versions prior to 1.23, the Certificate.Leaf field was left nil, and the parsed
// certificate was discarded. This behavior can be re-enabled by setting "x509keypairleaf=0" in the
// GODEBUG environment variable.
//
// Parameters:
// - keyPairTlS: The *tls.Certificate containing the certificate chain.
//
// Returns:
// - The parsed leaf x509 certificate.
// - An error if the certificate could not be parsed.
func getLeafCertificate(keyPairTLS *tls.Certificate) (*x509.Certificate, error) {
if keyPairTLS == nil {
return nil, errors.New("provided certificate is nil")
}
if keyPairTLS.Leaf != nil {
return keyPairTLS.Leaf, nil
}
if len(keyPairTLS.Certificate) == 0 {
return nil, errors.New("certificate chain is empty")
}
cert, err := x509.ParseCertificate(keyPairTLS.Certificate[0])
if err != nil {
return nil, err
}
return cert, nil
}