aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
+}