MFA fix, added IP blacklist, update webmail client
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
// Package smime implements S/MIME certificate-based email signing and encryption:
|
||||
// generating or importing a mailbox's own identity (certificate + private key),
|
||||
// signing outbound mail (RFC 8551 multipart/signed, detached CMS SignedData),
|
||||
// verifying a signature, encrypting outbound mail (application/pkcs7-mime, CMS
|
||||
// EnvelopedData), and decrypting it again.
|
||||
//
|
||||
// This package is deliberately certificate-chain-agnostic: it does not validate a
|
||||
// certificate against any CA trust store. A verified signature here means "this
|
||||
// message was cryptographically signed by the private key matching this exact
|
||||
// certificate," not "this certificate is trusted by a PKI" — the same posture this
|
||||
// codebase's own self-signed TLS certificate already has. Callers that want to
|
||||
// display a warning for unrecognized signers should compare the signer's certificate
|
||||
// against their own address book (see the mailbox_smime_contacts table in
|
||||
// internal/db), not chain validation.
|
||||
package smime
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"go.mozilla.org/pkcs7"
|
||||
pkcs12 "software.sslmate.com/src/go-pkcs12"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// go.mozilla.org/pkcs7 defaults ContentEncryptionAlgorithm to legacy DES-CBC for
|
||||
// backward compatibility with old clients — AES-256-GCM is the only acceptable
|
||||
// choice for anything generated here.
|
||||
pkcs7.ContentEncryptionAlgorithm = pkcs7.EncryptionAlgorithmAES256GCM
|
||||
}
|
||||
|
||||
// DefaultValidity mirrors a typical S/MIME certificate lifetime (1 year), matching
|
||||
// what most CAs issue for individual email certificates.
|
||||
const DefaultValidity = 365 * 24 * time.Hour
|
||||
|
||||
// GenerateSelfSigned creates a fresh RSA-2048 keypair and a self-signed certificate
|
||||
// scoped to email — same key size internal/tlsutil already uses for the server's own
|
||||
// TLS certificate. KeyUsage/ExtKeyUsage/EmailAddresses are set per RFC 8551 so
|
||||
// mainstream mail clients recognize it as a valid S/MIME certificate.
|
||||
func GenerateSelfSigned(email string, validity time.Duration) (certPEM, keyPEM []byte, err error) {
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("smime: generate key: %w", err)
|
||||
}
|
||||
serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("smime: generate serial: %w", err)
|
||||
}
|
||||
subject := pkix.Name{CommonName: email}
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serial,
|
||||
Subject: subject,
|
||||
Issuer: subject,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(validity),
|
||||
SignatureAlgorithm: x509.SHA256WithRSA,
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection},
|
||||
EmailAddresses: []string{email},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("smime: create certificate: %w", err)
|
||||
}
|
||||
keyDER, err := x509.MarshalPKCS8PrivateKey(priv)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("smime: marshal private key: %w", err)
|
||||
}
|
||||
certPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
keyPEM = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER})
|
||||
return certPEM, keyPEM, nil
|
||||
}
|
||||
|
||||
// ImportPKCS12 parses a .p12/.pfx bundle — the usual export format from a CA or
|
||||
// another mail client — into a certificate + private key. Only RSA keys are
|
||||
// supported (the only key type go.mozilla.org/pkcs7's encrypt/decrypt operations
|
||||
// actually support for key transport).
|
||||
func ImportPKCS12(data []byte, password string) (certPEM, keyPEM []byte, err error) {
|
||||
priv, cert, err := pkcs12.Decode(data, password)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("smime: decode PKCS#12: %w", err)
|
||||
}
|
||||
rsaKey, ok := priv.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, nil, errors.New("smime: only RSA keys are supported")
|
||||
}
|
||||
keyDER, err := x509.MarshalPKCS8PrivateKey(rsaKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("smime: marshal private key: %w", err)
|
||||
}
|
||||
certPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
|
||||
keyPEM = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER})
|
||||
return certPEM, keyPEM, nil
|
||||
}
|
||||
|
||||
// ParseCertPEM decodes a stored certificate back into a usable *x509.Certificate.
|
||||
func ParseCertPEM(certPEM []byte) (*x509.Certificate, error) {
|
||||
block, _ := pem.Decode(certPEM)
|
||||
if block == nil {
|
||||
return nil, errors.New("smime: invalid certificate PEM")
|
||||
}
|
||||
return x509.ParseCertificate(block.Bytes)
|
||||
}
|
||||
|
||||
// ParseKeyPEM decodes a stored private key back into a usable crypto.PrivateKey.
|
||||
// Tries PKCS#8 first (what GenerateSelfSigned/ImportPKCS12 both produce), falling
|
||||
// back to PKCS#1 for a hand-imported PEM that used the older format.
|
||||
func ParseKeyPEM(keyPEM []byte) (crypto.PrivateKey, error) {
|
||||
block, _ := pem.Decode(keyPEM)
|
||||
if block == nil {
|
||||
return nil, errors.New("smime: invalid private key PEM")
|
||||
}
|
||||
if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
return nil, errors.New("smime: unrecognized private key format")
|
||||
}
|
||||
Reference in New Issue
Block a user