-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ability to generate full set of certs for chia (#139)
* Add basic functions to load/generate/write signed certs * Generate the full list of required certs/keys * Fix lint issues
- Loading branch information
1 parent
07e84b6
commit f1c52af
Showing
5 changed files
with
297 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -146,3 +146,6 @@ main | |
bin/ | ||
test/ | ||
.idea/* | ||
/*.crt | ||
/*.key | ||
/test-cert-gen |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |