Port from heep; initial chekin
This commit is contained in:
commit
298f162d43
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.idea/
|
208
cert.go
Normal file
208
cert.go
Normal 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
64
certifier.go
Normal 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
10
go.mod
Normal 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
13
go.sum
Normal 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
55
grpc.go
Normal 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
62
key.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user