Commit ee9c51f9 by ale

Initial commit

0 parents
Showing with 305 additions and 0 deletions
This diff is collapsed. Click to expand it.
x509ca
======
A very simple command-line tool to generate arbitrary X509 certificates.
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"math/big"
"net"
"path/filepath"
"strings"
"syscall"
"time"
)
var (
caRoot = flag.String("root", "", "Root path for the CA")
isClient = flag.Bool("client", false, "Flag certificate for client usage")
isServer = flag.Bool("server", false, "Flag certificate for server usage")
validityDays = flag.Int("validity", 365, "Number of days new certs should be valid for")
outputCert = flag.String("output", "cert.pem", "Output path for certificate")
outputKey = flag.String("outkey", "private_key.pem", "Output path for private key")
subject = flag.String("subject", "", "X509 subject")
sanListStr = flag.String("san_list", "", "Subject-Alt-Name list, comma separated")
ipListStr = flag.String("ip_list", "", "IP list, comma separated")
)
const (
oneDay = 24 * time.Hour
pemCertificateType = "CERTIFICATE"
pemECPrivateKeyType = "EC PRIVATE KEY"
)
// MarshalCertificate encodes a certificate in PEM format.
func MarshalCertificate(cert *x509.Certificate) ([]byte, error) {
data := pem.EncodeToMemory(&pem.Block{Type: pemCertificateType, Bytes: cert.Raw})
return data, nil
}
// MarshalPrivateKey encodes a private key in PEM format.
func MarshalPrivateKey(priv *ecdsa.PrivateKey) ([]byte, error) {
der, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return nil, err
}
data := pem.EncodeToMemory(&pem.Block{Type: pemECPrivateKeyType, Bytes: der})
return data, nil
}
// UnmarshalCertificate reads a single X509 certificate encoded in PEM
// format from the given data.
func UnmarshalCertificate(data []byte) (*x509.Certificate, error) {
block, _ := pem.Decode(data)
if block == nil {
return nil, errors.New("no PEM block found in input")
}
if block.Type != pemCertificateType {
return nil, errors.New("encoded object is not a certificate")
}
certs, err := x509.ParseCertificates(block.Bytes)
if err != nil {
return nil, err
}
return certs[0], nil
}
// UnmarshalPrivateKey reads a PEM-encoded private key.
func UnmarshalPrivateKey(data []byte) (*ecdsa.PrivateKey, error) {
block, _ := pem.Decode(data)
if block == nil {
return nil, errors.New("no PEM block found in input")
}
if block.Type != pemECPrivateKeyType {
return nil, fmt.Errorf("encoded object is not an EC private key (%s)", block.Type)
}
return x509.ParseECPrivateKey(block.Bytes)
}
func loadCertificate(path string) (*x509.Certificate, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return UnmarshalCertificate(data)
}
func loadPrivateKey(path string) (*ecdsa.PrivateKey, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return UnmarshalPrivateKey(data)
}
func saveCertificate(cert *x509.Certificate, path string) error {
data, err := MarshalCertificate(cert)
if err != nil {
return err
}
return ioutil.WriteFile(path, data, 0644)
}
func savePrivateKey(pkey *ecdsa.PrivateKey, path string) error {
data, err := MarshalPrivateKey(pkey)
if err != nil {
return err
}
return ioutil.WriteFile(path, data, 0600)
}
// CA holds the private key for our certification authority.
type CA struct {
pkey *ecdsa.PrivateKey
cert *x509.Certificate
}
// Load or initialize CA.
func loadCA(path string) (*CA, error) {
keyPath := filepath.Join(path, "ca_private_key.pem")
certPath := filepath.Join(path, "ca.pem")
// Attempt to load.
var cert *x509.Certificate
pkey, err := loadPrivateKey(keyPath)
if err != nil {
// Initialize.
pkey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, fmt.Errorf("failed to generate ECDSA private key: %v", err)
}
now := time.Now().UTC()
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"Service-oriented Authentication"},
CommonName: "Certification Authority",
},
NotBefore: now.Add(-5 * time.Minute),
NotAfter: now.AddDate(20, 0, 0), // 20 years.
SignatureAlgorithm: x509.ECDSAWithSHA256,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLen: 1,
}
der, err := x509.CreateCertificate(rand.Reader, &template, &template, pkey.Public(), pkey)
if err != nil {
return nil, fmt.Errorf("could not self-sign CA certificate: %v", err)
}
cert, err = x509.ParseCertificate(der)
if err != nil {
// This would be weird.
return nil, err
}
// Save the new files.
if err := savePrivateKey(pkey, keyPath); err != nil {
return nil, err
}
if err := saveCertificate(cert, certPath); err != nil {
return nil, err
}
} else {
cert, err = loadCertificate(certPath)
if err != nil {
return nil, err
}
}
return &CA{pkey: pkey, cert: cert}, nil
}
// Create a new private key and signed certificate.
func (ca *CA) createCertificate(subj pkix.Name, altNames []string, ipAddrs []net.IP, isClient, isServer bool, validity time.Duration) (*ecdsa.PrivateKey, *x509.Certificate, error) {
pkey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate ECDSA key: %v", err)
}
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate serial number: %v", err)
}
var extUsage []x509.ExtKeyUsage
if isServer {
extUsage = append(extUsage, x509.ExtKeyUsageServerAuth)
}
if isClient {
extUsage = append(extUsage, x509.ExtKeyUsageClientAuth)
}
now := time.Now().UTC()
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: subj,
IPAddresses: ipAddrs,
DNSNames: altNames,
NotBefore: now.Add(-5 * time.Minute),
NotAfter: now.Add(validity),
SignatureAlgorithm: x509.ECDSAWithSHA256,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: extUsage,
PublicKey: pkey.PublicKey,
BasicConstraintsValid: true,
}
der, err := x509.CreateCertificate(rand.Reader, &template, ca.cert, pkey.Public(), ca.pkey)
if err != nil {
return nil, nil, fmt.Errorf("failed to sign certificate: %v", err)
}
cert, err := x509.ParseCertificate(der)
return pkey, cert, err
}
func parseSubject(s string) (*pkix.Name, error) {
var name pkix.Name
for _, token := range strings.Split(s, "/") {
if token == "" {
continue
}
switch {
case strings.HasPrefix(token, "CN="):
name.CommonName = token[3:]
case strings.HasPrefix(token, "C="):
name.Country = append(name.Country, token[2:])
case strings.HasPrefix(token, "O="):
name.Organization = append(name.Organization, token[2:])
case strings.HasPrefix(token, "OU="):
name.OrganizationalUnit = append(name.OrganizationalUnit, token[3:])
case strings.HasPrefix(token, "ST="):
name.Province = append(name.Province, token[3:])
case strings.HasPrefix(token, "L="):
name.Locality = append(name.Locality, token[2:])
default:
return nil, fmt.Errorf("unknown token '%s'", token)
}
}
return &name, nil
}
func main() {
log.SetFlags(0)
syscall.Umask(077)
flag.Parse()
// Sanity check command-line options.
if !*isClient && !*isServer {
log.Fatal("Must specify --client and/or --server")
}
if *caRoot == "" {
log.Fatal("Must specify --root")
}
if *subject == "" {
log.Fatal("Must specify --subject")
}
// Parse those command-line options that require processing.
parsedSubj, err := parseSubject(*subject)
if err != nil {
log.Fatalf("ERROR: could not parse --subject: %v", err)
}
var parsedSANList []string
if *sanListStr != "" {
parsedSANList = strings.Split(*sanListStr, ",")
}
var parsedIPList []net.IP
if *ipListStr != "" {
for _, ipStr := range strings.Split(*ipListStr, ",") {
ip := net.ParseIP(ipStr)
if ip == nil {
log.Fatalf("ERROR: could not parse IP %s: %v", ipStr, err)
}
parsedIPList = append(parsedIPList, ip)
}
}
ca, err := loadCA(*caRoot)
if err != nil {
log.Fatalf("ERROR: %v", err)
}
pkey, cert, err := ca.createCertificate(*parsedSubj, parsedSANList, parsedIPList, *isClient, *isServer, time.Duration(*validityDays)*oneDay)
if err != nil {
log.Fatalf("ERROR: %v", err)
}
if err := savePrivateKey(pkey, *outputKey); err != nil {
log.Fatalf("ERROR: %v", err)
}
if err := saveCertificate(cert, *outputCert); err != nil {
log.Fatalf("ERROR: %v", err)
}
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!