aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGibheer <gibheer@gmail.com>2015-03-16 17:11:28 +0100
committerGibheer <gibheer@gmail.com>2015-03-16 17:11:28 +0100
commit2c43111aecbf3f808e03c628ebf587994ce6b384 (patch)
tree4030ee781235af1e270c733af3184ea4807d05d6
parentb7f4f3ae2123301b6957498ef92d580f4b8ed105 (diff)
parent2b74dbb334192eb25ebd9de2d1273692797ec558 (diff)
Merge branch 'certificate'
This adds the API to generate certificates in the same way certificate sign requests were built. It works but still lacks some features and fields.
-rw-r--r--certificate.go126
-rw-r--r--certificate_test.go46
2 files changed, 172 insertions, 0 deletions
diff --git a/certificate.go b/certificate.go
new file mode 100644
index 0000000..b6fa252
--- /dev/null
+++ b/certificate.go
@@ -0,0 +1,126 @@
+package pki
+
+import (
+ "crypto/rand"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/pem"
+ "fmt"
+ "math/big"
+ "net"
+ "time"
+)
+
+const (
+ PemLabelCertificateRequest = "CERTIFICATE REQUEST"
+ PemLabelCertificate = "CERTIFICATE"
+)
+
+type (
+ CertificateData struct {
+ Subject pkix.Name
+
+ DNSNames []string
+ EmailAddresses []string
+ 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 {
+ 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) {
+ 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(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}
+ return pem.EncodeToMemory(block), nil
+}
+
+// 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,
+ 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.PrivateKey())
+ } else {
+ 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)
+}
+
+// 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
+}
+
+// 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
+}
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
+}