mirror of
https://github.com/go-i2p/onramp.git
synced 2025-07-12 10:55:29 -04:00
285 lines
9.1 KiB
Go
285 lines
9.1 KiB
Go
package onramp
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"math/big"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/cretz/bine/torutil"
|
|
)
|
|
|
|
// TLSKeys returns the TLS certificate and key for the given Garlic.
|
|
// if no TLS keys exist, they will be generated. They will be valid for
|
|
// the .b32.i2p domain.
|
|
func (g *Garlic) TLSKeys() (tls.Certificate, error) {
|
|
log.WithField("name", g.getName()).Debug("Getting TLS keys for Garlic service")
|
|
keys, err := g.Keys()
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to get I2P keys")
|
|
return tls.Certificate{}, err
|
|
}
|
|
base32 := keys.Addr().Base32()
|
|
log.WithField("base32", base32).Debug("Retrieving TLS certificate for base32 address")
|
|
return TLSKeys(base32)
|
|
}
|
|
|
|
// TLSKeys returns the TLS certificate and key for the given Onion.
|
|
// if no TLS keys exist, they will be generated. They will be valid for
|
|
// the .onion domain.
|
|
func (o *Onion) TLSKeys() (tls.Certificate, error) {
|
|
log.WithField("name", o.getName()).Debug("Getting TLS keys for Onion service")
|
|
keys, err := o.Keys()
|
|
if err != nil {
|
|
return tls.Certificate{}, err
|
|
}
|
|
onionService := torutil.OnionServiceIDFromPrivateKey(keys)
|
|
log.WithField("onion_service", onionService).Debug("Retrieving TLS certificate for onion service")
|
|
return TLSKeys(onionService)
|
|
}
|
|
|
|
// TLSKeys returns the TLS certificate and key for the given hostname.
|
|
func TLSKeys(tlsHost string) (tls.Certificate, error) {
|
|
log.WithField("host", tlsHost).Debug("Getting TLS certificate and key")
|
|
tlsCert := tlsHost + ".crt"
|
|
tlsKey := tlsHost + ".pem"
|
|
if err := CreateTLSCertificate(tlsHost); nil != err {
|
|
log.WithError(err).Error("Failed to create TLS certificate")
|
|
return tls.Certificate{}, err
|
|
}
|
|
tlsKeystorePath, err := TLSKeystorePath()
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to get TLS keystore path")
|
|
return tls.Certificate{}, err
|
|
}
|
|
tlsCertPath := filepath.Join(tlsKeystorePath, tlsCert)
|
|
tlsKeyPath := filepath.Join(tlsKeystorePath, tlsKey)
|
|
|
|
log.WithFields(logrus.Fields{
|
|
"cert_path": tlsCertPath,
|
|
"key_path": tlsKeyPath,
|
|
}).Debug("Loading TLS certificate pair")
|
|
|
|
cert, err := tls.LoadX509KeyPair(tlsCertPath, tlsKeyPath)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to load TLS certificate pair")
|
|
return cert, err
|
|
}
|
|
|
|
log.Debug("Successfully loaded TLS certificate and key")
|
|
return cert, nil
|
|
}
|
|
|
|
// CreateTLSCertificate generates a TLS certificate for the given hostname,
|
|
// and stores it in the TLS keystore for the application. If the keys already
|
|
// exist, generation is skipped.
|
|
func CreateTLSCertificate(tlsHost string) error {
|
|
log.WithField("host", tlsHost).Debug("Creating TLS certificate")
|
|
tlsCertName := tlsHost + ".crt"
|
|
tlsKeyName := tlsHost + ".pem"
|
|
tlsKeystorePath, err := TLSKeystorePath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tlsCert := filepath.Join(tlsKeystorePath, tlsCertName)
|
|
tlsKey := filepath.Join(tlsKeystorePath, tlsKeyName)
|
|
_, certErr := os.Stat(tlsCert)
|
|
_, keyErr := os.Stat(tlsKey)
|
|
if certErr != nil || keyErr != nil {
|
|
log.WithFields(logrus.Fields{
|
|
"cert_exists": certErr == nil,
|
|
"key_exists": keyErr == nil,
|
|
"cert_path": tlsCert,
|
|
"key_path": tlsKey,
|
|
}).Debug("Certificate or key missing, generating new ones")
|
|
if certErr != nil {
|
|
log.WithField("path", tlsCert).Debug("TLS certificate not found")
|
|
fmt.Printf("Unable to read TLS certificate '%s'\n", tlsCert)
|
|
}
|
|
if keyErr != nil {
|
|
log.WithField("path", tlsKey).Debug("TLS key not found")
|
|
fmt.Printf("Unable to read TLS key '%s'\n", tlsKey)
|
|
}
|
|
|
|
if err := createTLSCertificate(tlsHost); nil != err {
|
|
log.WithError(err).Error("Failed to create TLS certificate")
|
|
return err
|
|
}
|
|
} else {
|
|
log.Debug("TLS certificate and key already exist")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func createTLSCertificate(host string) error {
|
|
log.WithField("host", host).Debug("Generating new TLS certificate")
|
|
fmt.Println("Generating TLS keys. This may take a minute...")
|
|
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to generate private key")
|
|
return err
|
|
}
|
|
|
|
tlsCert, err := NewTLSCertificate(host, priv)
|
|
if nil != err {
|
|
log.WithError(err).Error("Failed to create new TLS certificate")
|
|
return err
|
|
}
|
|
privStore, err := TLSKeystorePath()
|
|
if nil != err {
|
|
log.WithError(err).Error("Failed to get keystore path")
|
|
return err
|
|
}
|
|
|
|
certFile := filepath.Join(privStore, host+".crt")
|
|
log.WithField("path", certFile).Debug("Saving TLS certificate")
|
|
// save the TLS certificate
|
|
certOut, err := os.Create(certFile)
|
|
if err != nil {
|
|
log.WithError(err).WithField("path", certFile).Error("Failed to create certificate file")
|
|
return fmt.Errorf("failed to open %s for writing: %s", host+".crt", err)
|
|
}
|
|
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: tlsCert})
|
|
certOut.Close()
|
|
log.WithField("path", certFile).Debug("TLS certificate saved successfully")
|
|
fmt.Printf("\tTLS certificate saved to: %s\n", host+".crt")
|
|
|
|
// save the TLS private key
|
|
privFile := filepath.Join(privStore, host+".pem")
|
|
log.WithField("path", privFile).Debug("Saving TLS private key")
|
|
keyOut, err := os.OpenFile(privFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
|
if err != nil {
|
|
log.WithError(err).WithField("path", privFile).Error("Failed to create private key file")
|
|
return fmt.Errorf("failed to open %s for writing: %v", privFile, err)
|
|
}
|
|
secp384r1, err := asn1.Marshal(asn1.ObjectIdentifier{1, 3, 132, 0, 34}) // http://www.ietf.org/rfc/rfc5480.txt
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to marshal EC parameters")
|
|
return err
|
|
}
|
|
pem.Encode(keyOut, &pem.Block{Type: "EC PARAMETERS", Bytes: secp384r1})
|
|
ecder, err := x509.MarshalECPrivateKey(priv)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to marshal private key")
|
|
return err
|
|
}
|
|
pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: ecder})
|
|
pem.Encode(keyOut, &pem.Block{Type: "CERTIFICATE", Bytes: tlsCert})
|
|
|
|
keyOut.Close()
|
|
log.WithField("path", privFile).Debug("TLS private key saved successfully")
|
|
fmt.Printf("\tTLS private key saved to: %s\n", privFile)
|
|
|
|
// CRL
|
|
crlFile := filepath.Join(privStore, host+".crl")
|
|
log.WithField("path", crlFile).Debug("Creating CRL")
|
|
crlOut, err := os.OpenFile(crlFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
|
if err != nil {
|
|
log.WithError(err).WithField("path", crlFile).Error("Failed to create CRL file")
|
|
return fmt.Errorf("failed to open %s for writing: %s", crlFile, err)
|
|
}
|
|
crlcert, err := x509.ParseCertificate(tlsCert)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to parse certificate for CRL creation")
|
|
return fmt.Errorf("Certificate with unknown critical extension was not parsed: %s", err)
|
|
}
|
|
|
|
now := time.Now()
|
|
revokedCerts := []pkix.RevokedCertificate{
|
|
{
|
|
SerialNumber: crlcert.SerialNumber,
|
|
RevocationTime: now,
|
|
},
|
|
}
|
|
|
|
crlBytes, err := crlcert.CreateCRL(rand.Reader, priv, revokedCerts, now, now)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to create CRL")
|
|
return fmt.Errorf("error creating CRL: %s", err)
|
|
}
|
|
_, err = x509.ParseDERCRL(crlBytes)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to validate generated CRL")
|
|
return fmt.Errorf("error reparsing CRL: %s", err)
|
|
}
|
|
pem.Encode(crlOut, &pem.Block{Type: "X509 CRL", Bytes: crlBytes})
|
|
crlOut.Close()
|
|
fmt.Printf("\tTLS CRL saved to: %s\n", crlFile)
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewTLSCertificate generates a new TLS certificate for the given hostname,
|
|
// returning it as bytes.
|
|
func NewTLSCertificate(host string, priv *ecdsa.PrivateKey) ([]byte, error) {
|
|
return NewTLSCertificateAltNames(priv, host)
|
|
}
|
|
|
|
// NewTLSCertificateAltNames generates a new TLS certificate for the given hostname,
|
|
// and a list of alternate names, returning it as bytes.
|
|
func NewTLSCertificateAltNames(priv *ecdsa.PrivateKey, hosts ...string) ([]byte, error) {
|
|
notBefore := time.Now()
|
|
notAfter := notBefore.Add(5 * 365 * 24 * time.Hour)
|
|
host := ""
|
|
if len(hosts) > 0 {
|
|
host = hosts[0]
|
|
}
|
|
|
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
template := x509.Certificate{
|
|
SerialNumber: serialNumber,
|
|
Subject: pkix.Name{
|
|
Organization: []string{"I2P Anonymous Network"},
|
|
OrganizationalUnit: []string{"I2P"},
|
|
Locality: []string{"XX"},
|
|
StreetAddress: []string{"XX"},
|
|
Country: []string{"XX"},
|
|
CommonName: host,
|
|
},
|
|
NotBefore: notBefore,
|
|
NotAfter: notAfter,
|
|
SignatureAlgorithm: x509.ECDSAWithSHA512,
|
|
|
|
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
BasicConstraintsValid: true,
|
|
IsCA: true,
|
|
DNSNames: hosts[1:],
|
|
}
|
|
|
|
hosts = append(hosts, strings.Split(host, ",")...)
|
|
for _, h := range hosts {
|
|
if ip := net.ParseIP(h); ip != nil {
|
|
template.IPAddresses = append(template.IPAddresses, ip)
|
|
} else {
|
|
template.DNSNames = append(template.DNSNames, h)
|
|
}
|
|
}
|
|
|
|
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return derBytes, nil
|
|
}
|