Port from heep; initial chekin

This commit is contained in:
Thilo Karraß 2022-11-04 19:38:34 +01:00
commit 298f162d43
7 changed files with 413 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.idea/

208
cert.go Normal file
View File

@ -0,0 +1,208 @@
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{
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{
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{
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
}

64
certifier.go Normal file
View File

@ -0,0 +1,64 @@
package certifer
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/binary"
"encoding/gob"
"os"
)
type certifier struct {
key string
// head / thing / bla (
// orga / unit / cn
// "abc"/ "ca" / "ca"
// Orga will be the Organization of newly created CAs
Orga string
// Thing is the OrgaUnit of newly created Certs
Thing string
}
func New(key, orga, thing string) *certifier {
return &certifier{
key: key,
Orga: orga,
Thing: thing,
}
}
func (x *certifier) Import(data []byte) (*Cert, error) {
blob := make([]byte, len(data))
copy(blob, data)
tKeydata := sha256.Sum256([]byte(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.NewCBCDecrypter(block, tIVdata)
cbc.CryptBlocks(blob, blob)
bloblen := binary.BigEndian.Uint32(blob[0:4])
buf := bytes.NewBuffer(blob[4 : 4+bloblen])
// should be plain gob now
cert := &Cert{}
tGob := gob.NewDecoder(buf)
if err := tGob.Decode(cert); err != nil {
return nil, err
}
return cert, nil
}
func (x *certifier) ImportFromFile(filename string) (*Cert, error) {
blob, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
return x.Import(blob)
}

10
go.mod Normal file
View File

@ -0,0 +1,10 @@
module udico.de/heep/cert
go 1.19
require google.golang.org/grpc v1.50.1
require (
github.com/golang/protobuf v1.5.2 // indirect
google.golang.org/protobuf v1.27.1 // indirect
)

13
go.sum Normal file
View File

@ -0,0 +1,13 @@
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY=
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=

55
grpc.go Normal file
View File

@ -0,0 +1,55 @@
package certifer
import (
"crypto/tls"
"crypto/x509"
"errors"
"google.golang.org/grpc/credentials"
)
func (c *Cert) GrpcServerConfig() (credentials.TransportCredentials, error) {
if c.CA == nil {
return nil, errors.New("security blob contains no CA")
}
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(c.CA.CertAsPem()) {
return nil, errors.New("cannot add CA to pool")
}
tCertPem := c.CertAsPem()
tKeyPem, _ := c.KeyAsPem()
tCert, err := tls.X509KeyPair(tCertPem, tKeyPem)
if err != nil {
return nil, err
}
config := &tls.Config{
Certificates: []tls.Certificate{tCert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: certPool,
}
creds := credentials.NewTLS(config)
return creds, nil
}
func (c *Cert) GrpcClientConfig() (credentials.TransportCredentials, error) {
if c.CA == nil {
return nil, errors.New("security blob contains no CA")
}
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(c.CA.CertAsPem()) {
return nil, errors.New("cannot add CA to pool")
}
tCertPem := c.CertAsPem()
tKeyPem, _ := c.KeyAsPem()
tCert, err := tls.X509KeyPair(tCertPem, tKeyPem)
if err != nil {
return nil, err
}
config := &tls.Config{
Certificates: []tls.Certificate{tCert},
RootCAs: certPool,
}
creds := credentials.NewTLS(config)
return creds, nil
}

62
key.go Normal file
View File

@ -0,0 +1,62 @@
package certifer
import (
"crypto/ecdsa"
"crypto/elliptic"
"math/big"
"math/rand"
"time"
)
const maxCreateKeyRounds = 3
var ecdsacurve = elliptic.P256()
// KeyParameters hold the ecdsa curve parameters of private (and public, despite these are redundand values) keys.
type KeyParameters struct {
D, X, Y *big.Int
}
// NewKeyParameters extracts key information from a given ecdsa private key and returns a KeyParameters instance.
func NewKeyParameters(key *ecdsa.PrivateKey) *KeyParameters {
return &KeyParameters{
D: key.D,
X: key.X,
Y: key.Y,
}
}
// Key creates a new ecdsa.PrivateKey from the given KeyParameters
func (k *KeyParameters) Key() *ecdsa.PrivateKey {
priv := new(ecdsa.PrivateKey)
priv.PublicKey.Curve = ecdsacurve
priv.D = k.D
priv.PublicKey.X, priv.PublicKey.Y = k.X, k.Y
return priv
}
func createKeyPair() *ecdsa.PrivateKey {
// generate key pair
var pk *ecdsa.PrivateKey
var err error
for i := 0; i < 3; i++ {
r := getRandom()
pk, err = ecdsa.GenerateKey(ecdsacurve, r)
if err != nil {
//log.WithError(err).Error("cannot create key pair")
continue
}
return pk
}
panic(err)
}
var theRand *rand.Rand = nil
func getRandom() *rand.Rand {
if theRand == nil {
rnd := rand.NewSource(time.Now().Unix())
theRand = rand.New(rnd)
}
return theRand
}