From 09a8380b7a4165bb4004f4d3efe87ac1fa151be9 Mon Sep 17 00:00:00 2001 From: Gibheer Date: Sun, 15 Mar 2015 20:06:47 +0100 Subject: [PATCH 1/5] first draft of a certificate system --- certificate.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 certificate.go diff --git a/certificate.go b/certificate.go new file mode 100644 index 0000000..6537173 --- /dev/null +++ b/certificate.go @@ -0,0 +1,45 @@ +package pki + +import ( +// "crypto/x509/pkix" + "errors" + "net" +) + +var ( + ErrTypeMisMatch = errors.New("types mismatched") +) + +type ( + CertificateData struct { + // required fields + SerialNumber string + CommonName string + + // alternative data + DNSNames []string + EmailAddresses []string + IPAddresses []net.IP + + // address data + Country []string + Province []string + Locality []string + PostalCode []string + StreetAddress []string + Organization []string + OrganizationalUnit []string + } +) + +// create a certificate sign request with the certificate data +//func (c *CertificateData) CreateCertificateRequest(priv PrivateKey) (*Certificate, error) { +// csr := x509.CertificateRequest{} +// csr.Subject := c.createSubject() +//} +// +//// create a pkix.Name for the subject of a cert or csr +//func (c *CertificateData) createSubject() (pkix.Name) { +// name := pkix.Name{} +// errors := make([]error, 0) +//} From e0ec6b1bef22bbdb9c1c773f6d33b5e1f3d2807f Mon Sep 17 00:00:00 2001 From: Gibheer Date: Sun, 15 Mar 2015 20:45:35 +0100 Subject: [PATCH 2/5] refine certificate sign request workings --- certificate.go | 72 ++++++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/certificate.go b/certificate.go index 6537173..452c3ee 100644 --- a/certificate.go +++ b/certificate.go @@ -1,45 +1,55 @@ package pki import ( -// "crypto/x509/pkix" - "errors" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" "net" ) -var ( - ErrTypeMisMatch = errors.New("types mismatched") -) +const PemLabelCertificateRequest = "CERTIFICATE REQUEST" type ( CertificateData struct { - // required fields - SerialNumber string - CommonName string + Subject pkix.Name - // alternative data - DNSNames []string + DnsNames []string EmailAddresses []string - IPAddresses []net.IP - - // address data - Country []string - Province []string - Locality []string - PostalCode []string - StreetAddress []string - Organization []string - OrganizationalUnit []string + IpAddresses []net.IP } + + Certificate x509.Certificate + CertificateRequest x509.CertificateRequest ) -// create a certificate sign request with the certificate data -//func (c *CertificateData) CreateCertificateRequest(priv PrivateKey) (*Certificate, error) { -// csr := x509.CertificateRequest{} -// csr.Subject := c.createSubject() -//} -// -//// create a pkix.Name for the subject of a cert or csr -//func (c *CertificateData) createSubject() (pkix.Name) { -// name := pkix.Name{} -// errors := make([]error, 0) -//} +// Create a certificate sign request from the input data and the private key of +// the request creator. +func (c *CertificateData) ToCertificateRequest(private_key PrivateKey) (*CertificateRequest, error) { + csr := &x509.CertificateRequest{} + + csr.Subject = c.Subject + csr.DNSNames = c.DnsNames + csr.IPAddresses = c.IpAddresses + csr.EmailAddresses = c.EmailAddresses + + csr_asn1, err := x509.CreateCertificateRequest(rand.Reader, csr, private_key.PrivateKey()) + if err != nil { return nil, err } + return LoadCertificateSignRequest(csr_asn1) +} + +// Load a certificate sign request from its asn1 representation. +func LoadCertificateSignRequest(raw []byte) (*CertificateRequest, error) { + csr, err = x509.ParseCertificateRequest(csr_asn1) + if err != nil { return nil, err } + return (*CertificateRequest)(csr), nil +} + +// Return the certificate sign request as a pem block. +func (c *CertificateRequest) MarshalPem() (marshalledPemBlock, error) { + block := pem.Block{Type: PemLabelCertificateRequest, Bytes: c.Raw} + return pem.EncodeToMemory(block), nil +} + +func (c *CertificateRequest) ToCertificate() { +} From d4d2d4c09ba082113e0ea232149d50c9c8b9cbfd Mon Sep 17 00:00:00 2001 From: Gibheer Date: Sun, 15 Mar 2015 21:38:04 +0100 Subject: [PATCH 3/5] add certificate functionality --- certificate.go | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/certificate.go b/certificate.go index 452c3ee..b597834 100644 --- a/certificate.go +++ b/certificate.go @@ -23,6 +23,10 @@ type ( CertificateRequest x509.CertificateRequest ) +func NewCertificateData() *CertificateData { + return &CertificateData{Subject: pkix.Name{}} +} + // Create a certificate sign request from the input data and the private key of // the request creator. func (c *CertificateData) ToCertificateRequest(private_key PrivateKey) (*CertificateRequest, error) { @@ -40,16 +44,42 @@ func (c *CertificateData) ToCertificateRequest(private_key PrivateKey) (*Certifi // Load a certificate sign request from its asn1 representation. func LoadCertificateSignRequest(raw []byte) (*CertificateRequest, error) { - csr, err = x509.ParseCertificateRequest(csr_asn1) + csr, err := x509.ParseCertificateRequest(raw) if err != nil { return nil, err } return (*CertificateRequest)(csr), nil } // Return the certificate sign request as a pem block. func (c *CertificateRequest) MarshalPem() (marshalledPemBlock, error) { - block := pem.Block{Type: PemLabelCertificateRequest, Bytes: c.Raw} + block := &pem.Block{Type: PemLabelCertificateRequest, Bytes: c.Raw} return pem.EncodeToMemory(block), nil } -func (c *CertificateRequest) ToCertificate() { +// Convert the certificate sign request to a certificate using the private key +// of the signer and the certificate of the signer. +// If the certificate is null, the sign request will be used to sign itself. +// For more information, please read http://golang.org/pkg/crypto/x509/#CreateCertificate +func (c *CertificateRequest) ToCertificate(private_key PrivateKey, ca *Certificate) (*Certificate, error) { + template := &x509.Certificate{} + template.Subject = c.Subject + template.DNSNames = c.DNSNames + template.IPAddresses = c.IPAddresses + template.EmailAddresses = c.EmailAddresses + + var cert_asn1 []byte + var err error + if ca == nil { + cert_asn1, err = x509.CreateCertificate(rand.Reader, template, template, c.PublicKey, private_key) + } else { + cert_asn1, err = x509.CreateCertificate(rand.Reader, template, (*x509.Certificate)(ca), c.PublicKey, private_key) + } + if err != nil { return nil, err } + return LoadCertificate(cert_asn1) +} + +// Load a certificate from its asn1 representation. +func LoadCertificate(raw []byte) (*Certificate, error) { + cert, err := x509.ParseCertificate(raw) + if err != nil { return nil, err } + return (*Certificate)(cert), nil } From 362fe8ff381893f8396090435ae77fd0a2492b4a Mon Sep 17 00:00:00 2001 From: Gibheer Date: Mon, 16 Mar 2015 16:48:42 +0100 Subject: [PATCH 4/5] finalize creation of a certificate With the options it is now finished. The only stuff left to do is to add all options provided by the go API. But this should be sufficient. --- certificate.go | 46 ++++++++++++++++++++++++++++++++++++++------- certificate_test.go | 46 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 certificate_test.go diff --git a/certificate.go b/certificate.go index b597834..e118833 100644 --- a/certificate.go +++ b/certificate.go @@ -5,7 +5,10 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "fmt" + "math/big" "net" + "time" ) const PemLabelCertificateRequest = "CERTIFICATE REQUEST" @@ -14,13 +17,20 @@ type ( CertificateData struct { Subject pkix.Name - DnsNames []string + DNSNames []string EmailAddresses []string - IpAddresses []net.IP + IPAddresses []net.IP } Certificate x509.Certificate CertificateRequest x509.CertificateRequest + + CertificateOptions struct { + SerialNumber *big.Int + NotBefore time.Time + NotAfter time.Time // Validity bounds. + KeyUsage x509.KeyUsage + } ) func NewCertificateData() *CertificateData { @@ -33,8 +43,8 @@ func (c *CertificateData) ToCertificateRequest(private_key PrivateKey) (*Certifi csr := &x509.CertificateRequest{} csr.Subject = c.Subject - csr.DNSNames = c.DnsNames - csr.IPAddresses = c.IpAddresses + csr.DNSNames = c.DNSNames + csr.IPAddresses = c.IPAddresses csr.EmailAddresses = c.EmailAddresses csr_asn1, err := x509.CreateCertificateRequest(rand.Reader, csr, private_key.PrivateKey()) @@ -58,20 +68,37 @@ func (c *CertificateRequest) MarshalPem() (marshalledPemBlock, error) { // Convert the certificate sign request to a certificate using the private key // of the signer and the certificate of the signer. // If the certificate is null, the sign request will be used to sign itself. +// Please also see the certificate options struct for information on mandatory fields. // For more information, please read http://golang.org/pkg/crypto/x509/#CreateCertificate -func (c *CertificateRequest) ToCertificate(private_key PrivateKey, ca *Certificate) (*Certificate, error) { +func (c *CertificateRequest) ToCertificate(private_key PrivateKey, + cert_opts CertificateOptions, ca *Certificate) (*Certificate, error) { + + if err := cert_opts.Valid(); err != nil { return nil, err } + template := &x509.Certificate{} template.Subject = c.Subject template.DNSNames = c.DNSNames template.IPAddresses = c.IPAddresses template.EmailAddresses = c.EmailAddresses + // if no ca is given, we have to set IsCA to self sign + if ca == nil { + template.IsCA = true + } + + template.NotBefore = cert_opts.NotBefore + template.NotAfter = cert_opts.NotAfter + template.KeyUsage = cert_opts.KeyUsage + template.SerialNumber = cert_opts.SerialNumber + var cert_asn1 []byte var err error + // if we have no ca which can sign the cert, a self signed cert is wanted + // (or isn't it? Maybe we should split creation of the template? But that would be ugly) if ca == nil { - cert_asn1, err = x509.CreateCertificate(rand.Reader, template, template, c.PublicKey, private_key) + cert_asn1, err = x509.CreateCertificate(rand.Reader, template, template, c.PublicKey, private_key.PrivateKey()) } else { - cert_asn1, err = x509.CreateCertificate(rand.Reader, template, (*x509.Certificate)(ca), c.PublicKey, private_key) + cert_asn1, err = x509.CreateCertificate(rand.Reader, template, (*x509.Certificate)(ca), c.PublicKey, private_key.PrivateKey()) } if err != nil { return nil, err } return LoadCertificate(cert_asn1) @@ -83,3 +110,8 @@ func LoadCertificate(raw []byte) (*Certificate, error) { if err != nil { return nil, err } return (*Certificate)(cert), nil } + +func (co *CertificateOptions) Valid() error { + if co.SerialNumber == nil { return fmt.Errorf("No serial number set!") } + return nil +} diff --git a/certificate_test.go b/certificate_test.go new file mode 100644 index 0000000..3cb4a64 --- /dev/null +++ b/certificate_test.go @@ -0,0 +1,46 @@ +package pki + +import ( + "crypto/elliptic" +// "crypto/x509" + "crypto/x509/pkix" + "math/big" + "reflect" + "testing" +) + +var ( + TestCertificateData = CertificateData{ + Subject: pkix.Name{CommonName: "foobar"}, + DNSNames: []string{"foo.bar", "example.com"}, + } +) + +func TestCertificateCreation(t *testing.T) { + pk, err := NewPrivateKeyEcdsa(elliptic.P224()) + if err != nil { t.Errorf("cert: creating private key failed: %s", err) } + + csr, err := TestCertificateData.ToCertificateRequest(pk) + if err != nil { t.Errorf("cert: creating csr failed: %s", err) } + + cert_opts := CertificateOptions{ + // KeyUsage: x509.KeyUsageEncipherOnly | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign, + SerialNumber: big.NewInt(1), + } + + cert, err := csr.ToCertificate(pk, cert_opts, nil) + if err != nil { t.Errorf("cert: creating cert failed: %s", err) } + + if !fieldsAreSame(TestCertificateData, cert) { + t.Errorf("cert: Fields are not the same") + } +} + +func fieldsAreSame(data CertificateData, cert *Certificate) bool { + if data.Subject.CommonName != cert.Subject.CommonName { return false } + if !reflect.DeepEqual(data.Subject.Country, cert.Subject.Country) { return false } + if !reflect.DeepEqual(data.DNSNames, cert.DNSNames) { return false } + if !reflect.DeepEqual(data.IPAddresses, cert.IPAddresses) { return false } + if !reflect.DeepEqual(data.EmailAddresses, cert.EmailAddresses) { return false } + return true +} From 2b74dbb334192eb25ebd9de2d1273692797ec558 Mon Sep 17 00:00:00 2001 From: Gibheer Date: Mon, 16 Mar 2015 17:10:46 +0100 Subject: [PATCH 5/5] implement marshal pem interface for certificates --- certificate.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/certificate.go b/certificate.go index e118833..b6fa252 100644 --- a/certificate.go +++ b/certificate.go @@ -11,7 +11,10 @@ import ( "time" ) -const PemLabelCertificateRequest = "CERTIFICATE REQUEST" +const ( + PemLabelCertificateRequest = "CERTIFICATE REQUEST" + PemLabelCertificate = "CERTIFICATE" +) type ( CertificateData struct { @@ -111,6 +114,12 @@ func LoadCertificate(raw []byte) (*Certificate, error) { return (*Certificate)(cert), nil } +// marshal the certificate to a pem block +func (c *Certificate) MarshalPem() (marshalledPemBlock, error) { + block := &pem.Block{Type: PemLabelCertificate, Bytes: c.Raw} + return pem.EncodeToMemory(block), nil +} + func (co *CertificateOptions) Valid() error { if co.SerialNumber == nil { return fmt.Errorf("No serial number set!") } return nil