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