aboutsummaryrefslogtreecommitdiff
path: root/certificate.go
blob: b90e9fae25b5499ded7b821e0edd76d3d8ad2135 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package pki

import (
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"io"
	"math/big"
	"net"
	"time"
)

// labels used in the pem file format to mark certificate sign requests and certificates
const (
	PemLabelCertificateRequest = "CERTIFICATE REQUEST"
	PemLabelCertificate        = "CERTIFICATE"
)

type (
	// Use CertificateData to fill in the minimum data you need to create a certificate
	// sign request.
	CertificateData struct {
		Subject pkix.Name

		DNSNames       []string
		EmailAddresses []string
		IPAddresses    []net.IP
	}

	// Certificate is an alias on the x509.Certificate to add some methods.
	Certificate x509.Certificate
	// CertificateRequest is an alias on the x509.CertificateRequest to add some methods.
	CertificateRequest x509.CertificateRequest

	// CertificateOptions is used to provide the necessary information to create
	// a certificate from a certificate sign request.
	CertificateOptions struct {
		SerialNumber *big.Int
		NotBefore    time.Time
		NotAfter     time.Time // Validity bounds.
		IsCA         bool
		// how many sub ca are allowed between this ca and the end/final certificate
		// if it is -1, then no limit will be set
		CALength         int
		KeyUsage         x509.KeyUsage      // for what can the certificate be used
		KeyExtendedUsage []x509.ExtKeyUsage // extended usage for the certificate
		CRLUrls          []string
	}
)

// Create a new set of certificate data.
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
}

// ToPem returns a pem.Block representing the CertificateRequest.
func (c *CertificateRequest) ToPem() (pem.Block, error) {
	return pem.Block{Type: PemLabelCertificateRequest, Bytes: c.Raw}, nil
}

// Return the certificate sign request as a pem block.
func (c *CertificateRequest) MarshalPem() (io.WriterTo, error) {
	if block, err := c.ToPem(); err != nil {
		return nil, err
	} else {
		return marshalledPemBlock(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.ExtKeyUsage = cert_opts.KeyExtendedUsage
	template.CRLDistributionPoints = cert_opts.CRLUrls
	template.IsCA = cert_opts.IsCA
	if cert_opts.IsCA {
		template.BasicConstraintsValid = true
	}
	if cert_opts.CALength >= 0 {
		template.MaxPathLen = cert_opts.CALength
		template.MaxPathLenZero = true
		template.BasicConstraintsValid = true
	}
	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() (io.WriterTo, error) {
	if block, err := c.ToPem(); err != nil {
		return nil, err
	} else {
		return marshalledPemBlock(pem.EncodeToMemory(&block)), nil
	}
}

// ToPem returns the pem block of the certificate.
func (c *Certificate) ToPem() (pem.Block, error) {
	return pem.Block{Type: PemLabelCertificate, Bytes: c.Raw}, nil
}

// Check if the certificate options have the required fields set.
func (co *CertificateOptions) Valid() error {
	if co.SerialNumber == nil {
		return fmt.Errorf("No serial number set!")
	}
	return nil
}