Skip to content

Commit

Permalink
Merge pull request coreos#6 from unihorn/31
Browse files Browse the repository at this point in the history
feat(pkix): add certificate signing request and certificate for host
  • Loading branch information
yichengq committed Mar 11, 2014
2 parents 664ec99 + 6a0c4f3 commit 920702e
Show file tree
Hide file tree
Showing 18 changed files with 1,224 additions and 89 deletions.
33 changes: 14 additions & 19 deletions pkix/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ type Certificate struct {
// derBytes is always set for valid Certificate
derBytes []byte

pemBlock *pem.Block

crt *x509.Certificate
}

Expand All @@ -26,7 +24,7 @@ func NewCertificateFromDER(derBytes []byte) *Certificate {
return &Certificate{derBytes: derBytes}
}

// NewCertificateFromDER inits Certificate from PEM-format bytes
// NewCertificateFromPEM inits Certificate from PEM-format bytes
// data should contain at most one certificate
func NewCertificateFromPEM(data []byte) (c *Certificate, err error) {
pemBlock, _ := pem.Decode(data)
Expand All @@ -38,7 +36,7 @@ func NewCertificateFromPEM(data []byte) (c *Certificate, err error) {
err = errors.New("unmatched type or headers")
return
}
c = &Certificate{derBytes: pemBlock.Bytes, pemBlock: pemBlock}
c = &Certificate{derBytes: pemBlock.Bytes}
return
}

Expand All @@ -59,8 +57,8 @@ func (c *Certificate) buildX509Certificate() error {
return nil
}

// GetRawCrt gets crypto/x509-format Certificate
func (c *Certificate) GetRawCrt() (*x509.Certificate, error) {
// GetRawCertificate returns a copy of this certificate as an x509.Certificate
func (c *Certificate) GetRawCertificate() (*x509.Certificate, error) {
if err := c.buildX509Certificate(); err != nil {
return nil, err
}
Expand Down Expand Up @@ -102,7 +100,12 @@ func (c *Certificate) VerifyHost(hostCert *Certificate, name string) error {
KeyUsages: nil,
}

chains, err := hostCert.crt.Verify(verifyOpts)
rawHostCrt, err := hostCert.GetRawCertificate()
if err != nil {
return err
}

chains, err := rawHostCrt.Verify(verifyOpts)
if err != nil {
return err
}
Expand All @@ -113,24 +116,16 @@ func (c *Certificate) VerifyHost(hostCert *Certificate, name string) error {
return nil
}

func (c *Certificate) buildPEM() {
if c.pemBlock != nil {
return
}

c.pemBlock = &pem.Block{
// Export returns PEM-format bytes
func (c *Certificate) Export() ([]byte, error) {
pemBlock := &pem.Block{
Type: certificatePEMBlockType,
Headers: nil,
Bytes: c.derBytes,
}
}

// Export returns PEM-format bytes
func (c *Certificate) Export() ([]byte, error) {
c.buildPEM()

buf := new(bytes.Buffer)
if err := pem.Encode(buf, c.pemBlock); err != nil {
if err := pem.Encode(buf, pemBlock); err != nil {
return nil, err
}
return buf.Bytes(), nil
Expand Down
16 changes: 10 additions & 6 deletions pkix/cert_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
const (
// hostname used by CA certificate
authHostname = "CA"
// SerialNumber to start when signing certificate request
authStartSerialNumber = 2
)

var (
Expand Down Expand Up @@ -58,17 +60,19 @@ var (
}
)

func CreateCertificateAuthority(key *Key) (*Certificate, error) {
subjectKeyId, err := key.GenerateSubjectKeyId()
// CreateCertificateAuthority creates Certificate Authority using existing key.
// CertificateAuthorityInfo returned is the extra infomation required by Certificate Authority.
func CreateCertificateAuthority(key *Key) (*Certificate, *CertificateAuthorityInfo, error) {
subjectKeyId, err := GenerateSubjectKeyId(key.Public)
if err != nil {
return nil, err
return nil, nil, err
}
authTemplate.SubjectKeyId = subjectKeyId

crtBytes, err := x509.CreateCertificate(rand.Reader, &authTemplate, &authTemplate, key.pub, key.priv)
crtBytes, err := x509.CreateCertificate(rand.Reader, &authTemplate, &authTemplate, key.Public, key.Private)
if err != nil {
return nil, err
return nil, nil, err
}

return NewCertificateFromDER(crtBytes), nil
return NewCertificateFromDER(crtBytes), NewCertificateAuthorityInfo(authStartSerialNumber), nil
}
9 changes: 6 additions & 3 deletions pkix/cert_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ func TestCreateCertificateAuthority(t *testing.T) {
t.Fatal("Failed creating rsa key:", err)
}

crt, err := CreateCertificateAuthority(key)
crt, info, err := CreateCertificateAuthority(key)
if err != nil {
t.Fatal("Failed creating certificate authority:", err)
}

rawCrt, err := crt.GetRawCrt()
rawCrt, err := crt.GetRawCertificate()
if err != nil {
t.Fatal("Failed to get x509.Certificate:", err)
}
Expand All @@ -36,4 +35,8 @@ func TestCreateCertificateAuthority(t *testing.T) {
if !time.Now().Before(rawCrt.NotAfter) {
t.Fatal("Failed to be before NotAfter")
}

if info.SerialNumber.Uint64() != authStartSerialNumber {
t.Fatal("Failed to set serial number")
}
}
76 changes: 76 additions & 0 deletions pkix/cert_host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package pkix

import (
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
"time"
)

var (
// Build CA based on RFC5280
hostTemplate = x509.Certificate{
// **SHOULD** be filled in a unique number
SerialNumber: big.NewInt(0),
// **SHOULD** be filled in host info
Subject: pkix.Name{},
// NotBefore is set to be 10min earlier to fix gap on time difference in cluster
NotBefore: time.Now().Add(-600).UTC(),
// 10-year lease
NotAfter: time.Now().AddDate(10, 0, 0).UTC(),
// Used for certificate signing only
KeyUsage: 0,

ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
},
UnknownExtKeyUsage: nil,

// activate CA
BasicConstraintsValid: false,

// 160-bit SHA-1 hash of the value of the BIT STRING subjectPublicKey
// (excluding the tag, length, and number of unused bits)
// **SHOULD** be filled in later
SubjectKeyId: nil,

// Subject Alternative Name
DNSNames: nil,

PermittedDNSDomainsCritical: false,
PermittedDNSDomains: nil,
}
)

// CreateCertificateHost creates certificate for host.
// The arguments include CA certificate, CA certificate info, CA key, certificate request.
func CreateCertificateHost(crtAuth *Certificate, info *CertificateAuthorityInfo, keyAuth *Key, csr *CertificateSigningRequest) (*Certificate, error) {
hostTemplate.SerialNumber.Set(info.SerialNumber)
info.IncSerialNumber()

rawCsr, err := csr.GetRawCertificateSigningRequest()
if err != nil {
return nil, err
}

hostTemplate.Subject = rawCsr.Subject

hostTemplate.SubjectKeyId, err = GenerateSubjectKeyId(rawCsr.PublicKey)
if err != nil {
return nil, err
}

rawCrtAuth, err := crtAuth.GetRawCertificate()
if err != nil {
return nil, err
}

crtHostBytes, err := x509.CreateCertificate(rand.Reader, &hostTemplate, rawCrtAuth, rawCsr.PublicKey, keyAuth.Private)
if err != nil {
return nil, err
}

return NewCertificateFromDER(crtHostBytes), nil
}
48 changes: 48 additions & 0 deletions pkix/cert_host_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package pkix

import (
"testing"
)

func TestCreateCertificateHost(t *testing.T) {
crtAuth, err := NewCertificateFromPEM([]byte(certAuthPEM))
if err != nil {
t.Fatal("Failed to parse certificate from PEM:", err)
}

key, err := NewKeyFromRSAPrivateKeyPEM([]byte(rsaPrivKeyAuthPEM))
if err != nil {
t.Fatal("Failed parsing RSA private key:", err)
}

csr, err := NewCertificateSigningRequestFromPEM([]byte(csrPEM))
if err != nil {
t.Fatal("Failed parsing certificate request from PEM:", err)
}

crt, err := CreateCertificateHost(crtAuth, NewCertificateAuthorityInfo(authStartSerialNumber), key, csr)
if err != nil {
t.Fatal("Failed creating certificate for host:", err)
}

rawCrt, err := crt.GetRawCertificate()
if err != nil {
t.Fatal("Failed to get x509.Certificate:", err)
}

rawCrtAuth, err := crtAuth.GetRawCertificate()
if err != nil {
t.Fatal("Failed to get x509.Certificate:", err)
}
if err = rawCrt.CheckSignatureFrom(rawCrtAuth); err != nil {
t.Fatal("Failed to check signature:", err)
}

if err = rawCrt.VerifyHostname(csrHostname); err != nil {
t.Fatal("Failed to verify hostname:", err)
}

if rawCrt.SerialNumber.Uint64() != authStartSerialNumber {
t.Fatal("Expect serial number %v instead of %v", authStartSerialNumber, rawCrt.SerialNumber)
}
}
34 changes: 34 additions & 0 deletions pkix/cert_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package pkix

import (
"math/big"
)

// CertificateAuthorityInfo includes extra information required for CA
type CertificateAuthorityInfo struct {
// SerialNumber that has been used so far
// Recorded to ensure all serial numbers issued by the CA are different
SerialNumber *big.Int
}

func NewCertificateAuthorityInfo(serialNumber int64) *CertificateAuthorityInfo {
return &CertificateAuthorityInfo{big.NewInt(serialNumber)}
}

func NewCertificateAuthorityInfoFromJSON(data []byte) (*CertificateAuthorityInfo, error) {
i := big.NewInt(0)

if err := i.UnmarshalJSON(data); err != nil {
return nil, err
}

return &CertificateAuthorityInfo{i}, nil
}

func (n *CertificateAuthorityInfo) IncSerialNumber() {
n.SerialNumber.Add(n.SerialNumber, big.NewInt(1))
}

func (n *CertificateAuthorityInfo) Export() ([]byte, error) {
return n.SerialNumber.MarshalJSON()
}
44 changes: 44 additions & 0 deletions pkix/cert_info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package pkix

import (
"encoding/base64"
"testing"
)

const (
serialNumber = 10
infoBASE64 = "MTA="
)

func TestCertificateAuthorityInfo(t *testing.T) {
i := NewCertificateAuthorityInfo(serialNumber)

i.IncSerialNumber()
if i.SerialNumber.Uint64() != serialNumber+1 {
t.Fatal("Failed incrementing serial number")
}
}

func TestCertificateAuthorityInfoFromJSON(t *testing.T) {
data, err := base64.StdEncoding.DecodeString(infoBASE64)
if err != nil {
t.Fatal("Failed decoding base64 string:", err)
}

i, err := NewCertificateAuthorityInfoFromJSON(data)
if err != nil {
t.Fatal("Failed init CertificateAuthorityInfo:", err)
}

if i.SerialNumber.Uint64() != serialNumber {
t.Fatal("Failed getting correct serial number")
}

b, err := i.Export()
if err != nil {
t.Fatal("Failed exporting info:", err)
}
if base64.StdEncoding.EncodeToString(b) != infoBASE64 {
t.Fatal("Failed exporting correct info")
}
}
Loading

0 comments on commit 920702e

Please sign in to comment.