diff --git a/.gitignore b/.gitignore index a1c7475..c9df1c7 100644 --- a/.gitignore +++ b/.gitignore @@ -146,3 +146,6 @@ main bin/ test/ .idea/* +/*.crt +/*.key +/test-cert-gen diff --git a/pkg/tls/certs.md b/pkg/tls/certs.md new file mode 100644 index 0000000..b1feb31 --- /dev/null +++ b/pkg/tls/certs.md @@ -0,0 +1,3 @@ +# About the private key here + +The key is intentionally committed to this repo. They're coincidentally also committed to chia-blockchain repo. Not a bug, don't open a bug-bounty submission about this, as it will be closed as a duplicate. diff --git a/pkg/tls/chia_ca.crt b/pkg/tls/chia_ca.crt new file mode 100644 index 0000000..ab2f357 --- /dev/null +++ b/pkg/tls/chia_ca.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDKTCCAhGgAwIBAgIUXIpxI5MoZQ65/vhc7DK/d5ymoMUwDQYJKoZIhvcNAQEL +BQAwRDENMAsGA1UECgwEQ2hpYTEQMA4GA1UEAwwHQ2hpYSBDQTEhMB8GA1UECwwY +T3JnYW5pYyBGYXJtaW5nIERpdmlzaW9uMB4XDTIxMDEyMzA4NTEwNloXDTMxMDEy +MTA4NTEwNlowRDENMAsGA1UECgwEQ2hpYTEQMA4GA1UEAwwHQ2hpYSBDQTEhMB8G +A1UECwwYT3JnYW5pYyBGYXJtaW5nIERpdmlzaW9uMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAzz/L219Zjb5CIKnUkpd2julGC+j3E97KUiuOalCH9wdq +gpJi9nBqLccwPCSFXFew6CNBIBM+CW2jT3UVwgzjdXJ7pgtu8gWj0NQ6NqSLiXV2 +WbpZovfrVh3x7Z4bjPgI3ouWjyehUfmK1GPIld4BfUSQtPlUJ53+XT32GRizUy+b +0CcJ84jp1XvyZAMajYnclFRNNJSw9WXtTlMUu+Z1M4K7c4ZPwEqgEnCgRc0TCaXj +180vo7mCHJQoDiNSCRATwfH+kWxOOK/nePkq2t4mPSFaX8xAS4yILISIOWYn7sNg +dy9D6gGNFo2SZ0FR3x9hjUjYEV3cPqg3BmNE3DDynQIDAQABoxMwETAPBgNVHRMB +Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAEugnFQjzHhS0eeCqUwOHmP3ww +/rXPkKF+bJ6uiQgXZl+B5W3m3zaKimJeyatmuN+5ST1gUET+boMhbA/7grXAsRsk +SFTHG0T9CWfPiuimVmGCzoxLGpWDMJcHZncpQZ72dcy3h7mjWS+U59uyRVHeiprE +hvSyoNSYmfvh7vplRKS1wYeA119LL5fRXvOQNW6pSsts17auu38HWQGagSIAd1UP +5zEvDS1HgvaU1E09hlHzlpdSdNkAx7si0DMzxKHUg9oXeRZedt6kcfyEmryd52Mj +1r1R9mf4iMIUv1zc2sHVc1omxnCw9+7U4GMWLtL5OgyJyfNyoxk3tC+D3KNU +-----END CERTIFICATE----- diff --git a/pkg/tls/chia_ca.key b/pkg/tls/chia_ca.key new file mode 100644 index 0000000..acf0798 --- /dev/null +++ b/pkg/tls/chia_ca.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDPP8vbX1mNvkIg +qdSSl3aO6UYL6PcT3spSK45qUIf3B2qCkmL2cGotxzA8JIVcV7DoI0EgEz4JbaNP +dRXCDON1cnumC27yBaPQ1Do2pIuJdXZZulmi9+tWHfHtnhuM+Ajei5aPJ6FR+YrU +Y8iV3gF9RJC0+VQnnf5dPfYZGLNTL5vQJwnziOnVe/JkAxqNidyUVE00lLD1Ze1O +UxS75nUzgrtzhk/ASqAScKBFzRMJpePXzS+juYIclCgOI1IJEBPB8f6RbE44r+d4 ++Sra3iY9IVpfzEBLjIgshIg5Zifuw2B3L0PqAY0WjZJnQVHfH2GNSNgRXdw+qDcG +Y0TcMPKdAgMBAAECggEAa6lLgEl3HyAQACHZUNGoADOEdNlvyP26gpcn42i0SQqs +NOpQyI67Sc6o6wVZ1g+j0ePGiCAW4RT4emVriSPi4Xc4bpiP6OAvKmOlXg96gUzo +z1H0EKnTsifaLsMssr2C9gDzlKhUsF3+1biEUf5DLcz5k1nWcsIrikqO1pizR2mL +nMuhW+kRwR6bY75pGlQ8JmqQ3BTS6YBMJ+BkZ8XOBcj9c8mvxTL4tVk4Wnc9dcd+ +dlkP1PkCM79WIF6pswW5ZDwGlU6pqk08qPzENjei/De6IBg8Lt4uJve79agV6Uxz +Y4xfRCWMunlj4VJwUcAzGhyjcBB1ZJi9JDBvnFmaDQKBgQDrMoBJ+vDcS0+2GQtH +k0GZ6wjhFttjqIVWFLmWgtvNO1K2+y6zwQjpFCAuAAUXXgfbVhIfFvp0HUxujjkE +3zeCYOlS8o9ESyWoPdg8woWjkZcya/4mE2jlj1+GI4SfHMska0EHUd4aaDk1en2a +vYKH27zD+yV+4IdyRsD0Bi93EwKBgQDhlHnODvf5QDVro+b3dsthkaP6F/s5oH/o +eLD1jEIGAN9Yz10njyLPfzaeiNPm6LSsKVXlgCjxQIuHTU2n0BsFke4F1ib0jOK4 +o3NN4rfVRb5O9vP50THv4+VhB9806FwiB371JeOhmitCSwiYp0wZmV3WURF4e4Fr +Cr3YlC+1jwKBgQC03BTCzvFAtbkKMp/13krn7VDaphT2wbQmybEdCGu1mhS1GNqE +57/OW+eS9/jySyCHjdxJhAX8HDuWGE/Ia03oOFWzr0p0HcVLZqNNtdfGPEKkR18c +MHjNbj7qi42EPUQJMWDEHDRK4jJ76UGFKI2jo1m46vueYVJGkhn2jHsbeQKBgDim +/Ew20Col6QSmfhwKFpvjYsYtfaeEWns8zFxupCozz+PS+Dc2KGzqKwJ3pJgqOy29 +l9fybtXf+uq5DFan2hF1C80lclUaiNoMGqol1TtXr6rPNIi59AumNXY/7tuvu2vE +bCsPH/L28ARPKdKEuYT4Umu/ol6aze7fHLymwrCbAoGAbm4vMLIy8wHYrAj5/een +YthUYbQ+XWOrrI1dJoGiwCIXTL4iBSO1ZjkR6M7yg9Zxv0BuqA+2tZqImLXsqIq/ +g7Jtrn7LfogS3KOiVa3QAHoXegMrRf4S3DyP/GW8E27MMvqBEkdCDX53h07wvPng +0NRspbyMaX43/tp8ZWkaZf0= +-----END PRIVATE KEY----- diff --git a/pkg/tls/tls.go b/pkg/tls/tls.go new file mode 100644 index 0000000..4683e5d --- /dev/null +++ b/pkg/tls/tls.go @@ -0,0 +1,244 @@ +package tls + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + // Need to embed the default config into the library + _ "embed" + "encoding/pem" + "fmt" + "math/big" + "os" + "path" + "time" +) + +var ( + privateNodeNames = []string{ + "full_node", + "wallet", + "farmer", + "harvester", + "timelord", + "crawler", + "data_layer", + "daemon", + } + publicNodeNames = []string{ + "full_node", + "wallet", + "farmer", + "introducer", + "timelord", + "data_layer", + } + + //go:embed chia_ca.crt + chiaCACrtBytes []byte + + //go:embed chia_ca.key + chiaCAKeyBytes []byte +) + +// GenerateAllCerts generates the full set of required certs for chia blockchain +func GenerateAllCerts(outDir string) error { + // First, ensure that all output directories exist + allNodes := append(privateNodeNames, publicNodeNames...) + for _, subdir := range append(allNodes, "ca") { + err := os.MkdirAll(path.Join(outDir, subdir), 0700) + if err != nil { + return fmt.Errorf("error making output directory for certs: %w", err) + } + } + + // Next, copy the chia_ca cert/key + err := os.WriteFile(path.Join(outDir, "ca", "chia_ca.crt"), chiaCACrtBytes, 0600) + if err != nil { + return fmt.Errorf("error copying chia_ca.crt: %w", err) + } + err = os.WriteFile(path.Join(outDir, "ca", "chia_ca.key"), chiaCAKeyBytes, 0600) + if err != nil { + return fmt.Errorf("error copying chia_ca.key: %w", err) + } + + chiaCACert, err := ParsePemCertificate(chiaCACrtBytes) + if err != nil { + return fmt.Errorf("error parsing chia_ca.crt") + } + + chiaCAKey, err := ParsePemKey(chiaCAKeyBytes) + if err != nil { + return fmt.Errorf("error parsing chia_ca.key") + } + + privateCACertBytes, privateCAKeyBytes, err := GenerateNewCA(path.Join(outDir, "ca", "private_ca")) + if err != nil { + return fmt.Errorf("error creating private ca pair: %w", err) + } + privateCACert, err := ParsePemCertificate(privateCACertBytes) + if err != nil { + return fmt.Errorf("error parsing generated private_ca.crt: %w", err) + } + privateCAKey, err := ParsePemKey(privateCAKeyBytes) + if err != nil { + return fmt.Errorf("error parsing generated private_ca.key: %w", err) + } + + for _, node := range publicNodeNames { + _, _, err = GenerateCASignedCert(chiaCACert, chiaCAKey, path.Join(outDir, node, fmt.Sprintf("public_%s", node))) + if err != nil { + return fmt.Errorf("error generating public pair for %s: %w", node, err) + } + } + + for _, node := range privateNodeNames { + _, _, err = GenerateCASignedCert(privateCACert, privateCAKey, path.Join(outDir, node, fmt.Sprintf("private_%s", node))) + if err != nil { + return fmt.Errorf("error generating private pair for %s: %w", node, err) + } + } + + return nil +} + +// ParsePemCertificate parses a certificate +func ParsePemCertificate(certPem []byte) (*x509.Certificate, error) { + // Load CA certificate + caCertBlock, rest := pem.Decode(certPem) + if caCertBlock == nil || caCertBlock.Type != "CERTIFICATE" { + return nil, fmt.Errorf("failed to decode CA certificate PEM") + } + if len(rest) != 0 { + return nil, fmt.Errorf("cert file had extra data at the end") + } + caCert, err := x509.ParseCertificate(caCertBlock.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse CA certificate: %v", err) + } + + return caCert, nil +} + +// ParsePemKey parses a key +func ParsePemKey(keyPem []byte) (*rsa.PrivateKey, error) { + // Load CA private key + caKeyBlock, rest := pem.Decode(keyPem) + if caKeyBlock == nil || caKeyBlock.Type != "PRIVATE KEY" { + return nil, fmt.Errorf("failed to decode CA private key PEM") + } + if len(rest) != 0 { + return nil, fmt.Errorf("key file had extra data at the end") + } + parsedKey, err := x509.ParsePKCS8PrivateKey(caKeyBlock.Bytes) + if err != nil { + return nil, fmt.Errorf("failed to parse CA private key: %v", err) + } + + caKey, ok := parsedKey.(*rsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("unexpected key type: %T", parsedKey) + } + + return caKey, nil +} + +// WriteCertAndKey Returns the written cert bytes, key bytes, and error +func WriteCertAndKey(certDER []byte, certKey *rsa.PrivateKey, certKeyBase string) ([]byte, []byte, error) { + // Write the new certificate to file + certOut := fmt.Sprintf("%s.crt", certKeyBase) + certPemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + if err := os.WriteFile(certOut, certPemBytes, 0600); err != nil { + return nil, nil, fmt.Errorf("failed to write cert PEM: %v", err) + } + + // Marshal private key to PKCS#8 + keyBytes, err := x509.MarshalPKCS8PrivateKey(certKey) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal private key to PKCS#8: %v", err) + } + + // Write the new private key to file in PKCS#8 format + keyOut := fmt.Sprintf("%s.key", certKeyBase) + keyPemBytes := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes}) + if err := os.WriteFile(keyOut, keyPemBytes, 0600); err != nil { + return nil, nil, fmt.Errorf("failed to write key PEM: %v", err) + } + + return certPemBytes, keyPemBytes, nil +} + +// GenerateNewCA generates a new CA +func GenerateNewCA(certKeyBase string) ([]byte, []byte, error) { + // Generate a new RSA private key + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + + // Create new certificate template + serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate serial number: %v", err) + } + + // Define the certificate template + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"Chia"}, + OrganizationalUnit: []string{"Organic Farming Division"}, + CommonName: "Chia CA", + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), // 10 years + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, + BasicConstraintsValid: true, + IsCA: true, + } + + // Create the self-signed certificate + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return nil, nil, err + } + + return WriteCertAndKey(certDER, privateKey, certKeyBase) +} + +// GenerateCASignedCert generates a new key/cert signed by the given CA +func GenerateCASignedCert(caCert *x509.Certificate, caKey *rsa.PrivateKey, certKeyBase string) ([]byte, []byte, error) { + // Generate new private key + certKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate private key: %v", err) + } + + // Create new certificate template + serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate serial number: %v", err) + } + certTemplate := x509.Certificate{ + Subject: pkix.Name{ + CommonName: "Chia", + Organization: []string{"Chia"}, + OrganizationalUnit: []string{"Organic Farming Division"}, + }, + SerialNumber: serialNumber, + NotBefore: time.Now().Add(-24 * time.Hour), + NotAfter: time.Date(2100, 8, 2, 0, 0, 0, 0, time.UTC), + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + BasicConstraintsValid: true, + DNSNames: []string{"chia.net"}, + } + + // Sign the new certificate + certDER, err := x509.CreateCertificate(rand.Reader, &certTemplate, caCert, &certKey.PublicKey, caKey) + if err != nil { + return nil, nil, fmt.Errorf("failed to create certificate: %v", err) + } + + return WriteCertAndKey(certDER, certKey, certKeyBase) +}