certifer/cert.go

212 lines
5.1 KiB
Go
Raw Normal View History

2022-11-04 19:38:34 +01:00
package certifer
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/binary"
"encoding/gob"
"encoding/pem"
"fmt"
"math/big"
"math/rand"
"net"
"os"
"time"
)
type Cert struct {
x *certifier
Template *x509.Certificate
Bytes []byte
Key *KeyParameters
Parent *Cert
CA *Cert
}
func (x *certifier) NewCA() (*Cert, error) {
ca := &x509.Certificate{
SerialNumber: big.NewInt(1337),
Subject: pkix.Name{
Organization: []string{x.Orga},
OrganizationalUnit: []string{"ca"},
CommonName: "root",
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 365),
IsCA: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
// up to this point this should be the bare minimum for a Parent cert
}
pk := createKeyPair()
// create certificate.
caBytes, err := x509.CreateCertificate(getRandom(), ca, ca, &pk.PublicKey, pk)
if err != nil {
return nil, err
}
return &Cert{
x: x,
2022-11-04 19:38:34 +01:00
Template: ca,
Bytes: caBytes,
Key: NewKeyParameters(pk),
}, nil
}
func (c *Cert) NewCert(cn string) (*Cert, error) {
cert := &x509.Certificate{
SerialNumber: big.NewInt(1337 + int64(rand.Uint64())),
Subject: pkix.Name{
Organization: []string{c.Template.Subject.Organization[0]},
OrganizationalUnit: []string{c.x.Thing},
CommonName: cn,
},
// DNSNames: []string{"bloq", "localhost"},
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 0, 0),
// SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}
pk := createKeyPair()
hash := sha1.Sum(pk.PublicKey.X.Bytes())
cert.SubjectKeyId = hash[:]
// caBytes, err := x509.CreateCertificate(r, ca, ca, &pk.PublicKey, pk)
certBytes, err := x509.CreateCertificate(getRandom(), cert, c.Template, &pk.PublicKey, c.Key.Key())
if err != nil {
return nil, err
}
ca := c.CA
if ca == nil {
ca = c
}
return &Cert{
x: c.x,
2022-11-04 19:38:34 +01:00
Template: cert,
Bytes: certBytes,
Key: NewKeyParameters(pk),
Parent: c,
CA: ca,
}, nil
}
func (c *Cert) CertAsPem() []byte {
p := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: c.Bytes,
})
return p
}
func (c *Cert) KeyAsPem() ([]byte, error) {
sec, err := x509.MarshalECPrivateKey(c.Key.Key())
if err != nil {
return nil, err
}
buf := pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: sec,
})
return buf, nil
}
// WithoutKeys returns a copy of this Cert tree without any private keys.
// Note: The result is NOT a deep copy of the original cert tree. Certificates, Templates and
// byte slices will still point to the original data, so be careful and do not modify!
func (c *Cert) WithoutKeys() *Cert {
ret := c.WithoutParentKeys()
ret.Key = nil
return ret
}
// WithoutParentKeys retruns a copy of this Cert tree without keys of the parent and ca.
// Note: The result is NOT a deep copy of the original cert tree. Certificates, Templates and
// byte slices will still point to the original data, so be careful and do not modify!
func (c *Cert) WithoutParentKeys() *Cert {
p := c.Parent
if p != nil {
p = p.WithoutKeys()
}
ca := c.CA
if ca != nil {
ca.WithoutKeys()
}
return &Cert{
x: c.x,
2022-11-04 19:38:34 +01:00
Template: c.Template,
Bytes: c.Bytes,
Key: c.Key,
Parent: p,
CA: ca,
}
}
func (c *Cert) Export(includeKey bool) ([]byte, error) {
exportCert := c
if !includeKey {
exportCert = c.WithoutParentKeys()
}
buf := &bytes.Buffer{}
tGob := gob.NewEncoder(buf)
if err := tGob.Encode(exportCert); err != nil {
return nil, err
}
blob := buf.Bytes()
bloblen := uint32(len(blob))
padding := (aes.BlockSize - ((len(blob) + 4) % aes.BlockSize)) % aes.BlockSize
paddedBlob := make([]byte, len(blob)+4+padding)
binary.BigEndian.PutUint32(paddedBlob[0:4], bloblen)
copy(paddedBlob[4:], blob)
//for i := 0; i < padding; i++ {
// use padding spaces. Nul bytes would confuse the json unmarshaller
//paddedBlob[len(paddedBlob)+i] = ' '
//}
tKeydata := sha256.Sum256([]byte(c.x.key)) // gives 32 bytes, which is a multiple of the block size
tIVdata := tKeydata[:aes.BlockSize]
block, err := aes.NewCipher(tKeydata[:])
if err != nil {
return nil, err
}
cbc := cipher.NewCBCEncrypter(block, tIVdata)
cbc.CryptBlocks(paddedBlob, paddedBlob)
return paddedBlob, nil
}
func (c *Cert) ExportToFile(includeKey bool, filename string) error {
cBlob, err := c.Export(includeKey)
if err != nil {
return err
}
if err := os.WriteFile(filename, cBlob, 0600); err != nil {
return err
}
return nil
}
func (c *Cert) String() string {
ret := ""
if c.Parent != nil {
ret += fmt.Sprintf("%v->", c.Parent)
}
key := ""
if c.Key != nil {
key = "(*)"
}
ret += fmt.Sprintf("[%v%v]", c.Template.Subject, key)
return ret
}