diff options
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/certificate/certificate.go | 58 | ||||
| -rw-r--r-- | pkg/easypki/easyca.go | 518 | ||||
| -rw-r--r-- | pkg/easypki/easyca_test.go | 144 | ||||
| -rw-r--r-- | pkg/easypki/easypki.go | 147 | ||||
| -rw-r--r-- | pkg/easypki/easypki_test.go | 162 | ||||
| -rw-r--r-- | pkg/easypki/template.go | 74 | ||||
| -rw-r--r-- | pkg/store/local.go | 383 | ||||
| -rw-r--r-- | pkg/store/store.go | 65 |
8 files changed, 889 insertions, 662 deletions
diff --git a/pkg/certificate/certificate.go b/pkg/certificate/certificate.go new file mode 100644 index 0000000..187fb1c --- /dev/null +++ b/pkg/certificate/certificate.go @@ -0,0 +1,58 @@ +// Copyright 2015 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package certificate provide helpers to manipulate certificates. +package certificate + +import ( + "crypto/rsa" + "crypto/x509" + "fmt" +) + +// Bundle represents a pair of private key and certificate. +type Bundle struct { + Name string + Key *rsa.PrivateKey + Cert *x509.Certificate +} + +// Raw returns the raw bytes for the private key and certificate. +func (b *Bundle) Raw() ([]byte, []byte) { + return x509.MarshalPKCS1PrivateKey(b.Key), b.Cert.Raw +} + +// RawToBundle creates a bundle from the name and bytes given for a private key +// and a certificate. +func RawToBundle(name string, key []byte, cert []byte) (*Bundle, error) { + k, err := x509.ParsePKCS1PrivateKey(key) + if err != nil { + return nil, fmt.Errorf("failed parsing private key from PEM bytes: %v", err) + } + c, err := x509.ParseCertificate(cert) + if err != nil { + return nil, fmt.Errorf("failed parsing certificate from PEM bytes: %v", err) + } + return &Bundle{Name: name, Key: k, Cert: c}, nil +} + +// State represents a certificate state (Valid, Expired, Revoked). +type State int + +// Certificate states. +const ( + Valid State = iota + Revoked + Expired +) diff --git a/pkg/easypki/easyca.go b/pkg/easypki/easyca.go deleted file mode 100644 index dbb6f0e..0000000 --- a/pkg/easypki/easyca.go +++ /dev/null @@ -1,518 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package easypki - -import ( - "bufio" - "crypto/rand" - "crypto/rsa" - "crypto/sha1" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "encoding/pem" - "fmt" - "io/ioutil" - "math/big" - "os" - "path/filepath" - "regexp" - "strings" - "time" -) - -var ( - // Index format - // 0 full string - // 1 Valid/Revoked/Expired - // 2 Expiration date - // 3 Revocation date - // 4 Serial - // 5 Filename - // 6 Subject - indexRegexp = regexp.MustCompile("^(V|R|E)\t([0-9]{12}Z)\t([0-9]{12}Z)?\t([0-9a-fA-F]{2,})\t([^\t]+)\t(.+)") -) - -func GeneratePrivateKey(path string) (*rsa.PrivateKey, error) { - keyFile, err := os.Create(path) - if err != nil { - return nil, fmt.Errorf("create %v: %v", path, err) - } - defer keyFile.Close() - - key, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return nil, fmt.Errorf("generate private key: %v", err) - } - err = pem.Encode(keyFile, &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(key), - }) - if err != nil { - return nil, fmt.Errorf("pem encode private key: %v", err) - } - return key, nil -} - -// GenerationRequest is a struct for providing configuration to -// GenerateCertificate when actioning a certification generation request. -type GenerationRequest struct { - PKIRoot string - Name string - Template *x509.Certificate - MaxPathLen int - IsIntermediateCA bool - IsClientCertificate bool -} - -// GenerateCertificate is a function for helping to generate new x509 -// certificates and keys from the GenerationRequest. This function renders the -// content out to the filesystem. -func GenerateCertificate(genReq *GenerationRequest) error { - // TODO(jclerc): check that pki has been init - - var crtPath string - privateKeyPath := filepath.Join(genReq.PKIRoot, "private", genReq.Name+".key") - if genReq.Name == "ca" { - crtPath = filepath.Join(genReq.PKIRoot, genReq.Name+".crt") - } else { - crtPath = filepath.Join(genReq.PKIRoot, "issued", genReq.Name+".crt") - } - - var caCrt *x509.Certificate - var caKey *rsa.PrivateKey - - if _, err := os.Stat(privateKeyPath); err == nil { - return fmt.Errorf("a key pair for %v already exists", genReq.Name) - } - - privateKey, err := GeneratePrivateKey(privateKeyPath) - if err != nil { - return fmt.Errorf("generate private key: %v", err) - } - - publicKeyBytes, err := asn1.Marshal(*privateKey.Public().(*rsa.PublicKey)) - if err != nil { - return fmt.Errorf("marshal public key: %v", err) - } - subjectKeyId := sha1.Sum(publicKeyBytes) - genReq.Template.SubjectKeyId = subjectKeyId[:] - - genReq.Template.NotBefore = time.Now() - genReq.Template.SignatureAlgorithm = x509.SHA256WithRSA - - // Non-intermediate Certificate Authority - if genReq.Template.IsCA && !genReq.IsIntermediateCA { - // Random Serial - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - return fmt.Errorf("failed to generate ca serial number: %s", err) - } - genReq.Template.SerialNumber = serialNumber - - // Root certificate can self-sign - genReq.Template.Issuer = genReq.Template.Subject - genReq.Template.AuthorityKeyId = genReq.Template.SubjectKeyId - - // Use the generated certificate template and private key (self-signing) - caCrt = genReq.Template - caKey = privateKey - } - - // Intermediate-only Certificate Authority - if genReq.Template.IsCA && genReq.IsIntermediateCA { - genReq.Template.ExtKeyUsage = []x509.ExtKeyUsage{ - x509.ExtKeyUsageClientAuth, - x509.ExtKeyUsageServerAuth, - } - } - - // Either type of Certificate Authority (intermediate, root, etc.) - if genReq.Template.IsCA || genReq.IsIntermediateCA { - genReq.Template.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageCRLSign - genReq.Template.BasicConstraintsValid = true - - // Enforce Maximum Path Length - if genReq.MaxPathLen >= 0 { - genReq.Template.MaxPathLen = genReq.MaxPathLen - genReq.Template.MaxPathLenZero = true // doesn't force to zero - } - } - - // Any leaf: intermediate CAs, client/server certificates, signed by a root - if !genReq.Template.IsCA || genReq.IsIntermediateCA { - serialNumber, err := NextNumber(genReq.PKIRoot, "serial") - if err != nil { - return fmt.Errorf("get next serial: %v", err) - } - genReq.Template.SerialNumber = serialNumber - - caCrt, caKey, err = GetCA(genReq.PKIRoot) - if err != nil { - return fmt.Errorf("get ca: %v", err) - } - } - - // Should cover only client/server (All non-CA, i.e. doesn't include intermediates) - if !genReq.Template.IsCA { - if !genReq.IsClientCertificate { - genReq.Template.ExtKeyUsage = append(genReq.Template.ExtKeyUsage, x509.ExtKeyUsageServerAuth) - } - // Clients can only use ClientAuth - genReq.Template.ExtKeyUsage = append(genReq.Template.ExtKeyUsage, x509.ExtKeyUsageClientAuth) - - // set the usage for non-CA certificates - genReq.Template.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment - } - - crt, err := x509.CreateCertificate(rand.Reader, genReq.Template, caCrt, privateKey.Public(), caKey) - if err != nil { - return fmt.Errorf("create certificate: %v", err) - } - - crtFile, err := os.Create(crtPath) - if err != nil { - return fmt.Errorf("create %v: %v", crtPath, err) - } - defer crtFile.Close() - - err = pem.Encode(crtFile, &pem.Block{ - Type: "CERTIFICATE", - Bytes: crt, - }) - if err != nil { - return fmt.Errorf("pem encode crt: %v", err) - } - - // I do not think we have to write the ca.crt in the index - if !genReq.Template.IsCA { - WriteIndex(genReq.PKIRoot, genReq.Name, genReq.Template) - if err != nil { - return fmt.Errorf("write index: %v", err) - } - } - return nil -} - -func GetCA(pkiroot string) (*x509.Certificate, *rsa.PrivateKey, error) { - caKeyBytes, err := ioutil.ReadFile(filepath.Join(pkiroot, "private", "ca.key")) - if err != nil { - return nil, nil, fmt.Errorf("read ca private key: %v", err) - } - p, _ := pem.Decode(caKeyBytes) - if p == nil { - return nil, nil, fmt.Errorf("pem decode did not found pem encoded ca private key") - } - caKey, err := x509.ParsePKCS1PrivateKey(p.Bytes) - if err != nil { - return nil, nil, fmt.Errorf("parse ca private key: %v", err) - } - - caCrtBytes, err := ioutil.ReadFile(filepath.Join(pkiroot, "ca.crt")) - if err != nil { - return nil, nil, fmt.Errorf("read ca crt: %v", err) - } - p, _ = pem.Decode(caCrtBytes) - if p == nil { - return nil, nil, fmt.Errorf("pem decode did not found pem encoded ca cert") - } - caCrt, err := x509.ParseCertificate(p.Bytes) - if err != nil { - return nil, nil, fmt.Errorf("parse ca crt: %v", err) - } - - return caCrt, caKey, nil -} - -func GenCRL(pkiroot string, expire int) error { - var revokedCerts []pkix.RevokedCertificate - f, err := os.Open(filepath.Join(pkiroot, "index.txt")) - if err != nil { - return err - } - defer f.Close() - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - matches := indexRegexp.FindStringSubmatch(scanner.Text()) - if len(matches) != 7 { - return fmt.Errorf("wrong line format %v elems: %v, %v", len(matches), matches, scanner.Text()) - } - if matches[1] != "R" { - continue - } - - crt, err := GetCertificate(filepath.Join(pkiroot, "issued", matches[5])) - if err != nil { - return fmt.Errorf("get certificate %v: %v", matches[5], err) - } - - matchedSerial := big.NewInt(0) - fmt.Sscanf(matches[4], "%X", matchedSerial) - if matchedSerial.Cmp(crt.SerialNumber) != 0 { - return fmt.Errorf("serial in index does not match revoked certificate: %v", matches[0]) - } - revocationTime, err := time.Parse("060102150405", strings.TrimSuffix(matches[3], "Z")) - if err != nil { - return fmt.Errorf("parse revocation time: %v", err) - } - revokedCerts = append(revokedCerts, pkix.RevokedCertificate{ - SerialNumber: crt.SerialNumber, - RevocationTime: revocationTime, - Extensions: crt.Extensions, - }) - } - caCrt, caKey, err := GetCA(pkiroot) - if err != nil { - return fmt.Errorf("get ca: %v", err) - } - crl, err := caCrt.CreateCRL(rand.Reader, caKey, revokedCerts, time.Now(), time.Now().AddDate(0, 0, expire)) - if err != nil { - return fmt.Errorf("create crl: %v", err) - } - // I do no see where we can pass it to CreateCRL, differs from openssl - crlNumber, err := NextNumber(pkiroot, "crlnumber") - if err != nil { - return fmt.Errorf("get next serial: %v", err) - } - - serialHexa := fmt.Sprintf("%X", crlNumber) - if len(serialHexa)%2 == 1 { - serialHexa = "0" + serialHexa - } - - crlPath := filepath.Join(pkiroot, "crl-"+serialHexa+".pem") - crlFile, err := os.Create(crlPath) - if err != nil { - return fmt.Errorf("create %v: %v", crlPath, err) - } - defer crlFile.Close() - - err = pem.Encode(crlFile, &pem.Block{ - Type: "X509 CRL", - Bytes: crl, - }) - if err != nil { - return fmt.Errorf("pem encode crt: %v", err) - } - - return nil -} - -func RevokeSerial(pkiroot string, serial *big.Int) error { - f, err := os.OpenFile(filepath.Join(pkiroot, "index.txt"), os.O_RDWR, 0644) - if err != nil { - return err - } - defer f.Close() - - var lines []string - scanner := bufio.NewScanner(f) - for scanner.Scan() { - matches := indexRegexp.FindStringSubmatch(scanner.Text()) - if len(matches) != 7 { - return fmt.Errorf("wrong line format") - } - matchedSerial := big.NewInt(0) - fmt.Sscanf(matches[4], "%X", matchedSerial) - if matchedSerial.Cmp(serial) == 0 { - if matches[1] == "R" { - return fmt.Errorf("certificate already revoked") - } else if matches[1] == "E" { - return fmt.Errorf("certificate already expired") - } - - lines = append(lines, fmt.Sprintf("R\t%v\t%vZ\t%v\t%v\t%v", - matches[2], - time.Now().UTC().Format("060102150405"), - matches[4], - matches[5], - matches[6])) - } else { - lines = append(lines, matches[0]) - } - } - - f.Truncate(0) - f.Seek(0, 0) - - for _, line := range lines { - n, err := fmt.Fprintln(f, line) - if err != nil { - return fmt.Errorf("write line: %v", err) - } - if n == 0 { - return fmt.Errorf("supposed to write [%v], written 0 bytes", line) - } - } - return nil -} - -func GetCertificate(path string) (*x509.Certificate, error) { - crtBytes, err := ioutil.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("read crt: %v", err) - } - p, _ := pem.Decode(crtBytes) - if p == nil { - return nil, fmt.Errorf("pem decode did not found pem encoded cert") - } - crt, err := x509.ParseCertificate(p.Bytes) - if err != nil { - return nil, fmt.Errorf("parse crt: %v", err) - } - - return crt, nil -} - -func WriteIndex(pkiroot, filename string, crt *x509.Certificate) error { - f, err := os.OpenFile(filepath.Join(pkiroot, "index.txt"), os.O_WRONLY|os.O_APPEND, 0644) - if err != nil { - return err - } - defer f.Close() - - serialOutput := fmt.Sprintf("%X", crt.SerialNumber) - // For compatibility with openssl we need an even length - if len(serialOutput)%2 == 1 { - serialOutput = "0" + serialOutput - } - - // Date format: yymmddHHMMSSZ - // E|R|V<tab>Expiry<tab>[RevocationDate]<tab>Serial<tab>filename<tab>SubjectDN - var subject string - if strs := crt.Subject.Country; len(strs) == 1 { - subject += "/C=" + strs[0] - } - if strs := crt.Subject.Organization; len(strs) == 1 { - subject += "/O=" + strs[0] - } - if strs := crt.Subject.OrganizationalUnit; len(strs) == 1 { - subject += "/OU=" + strs[0] - } - if strs := crt.Subject.Locality; len(strs) == 1 { - subject += "/L=" + strs[0] - } - if strs := crt.Subject.Province; len(strs) == 1 { - subject += "/ST=" + strs[0] - } - subject += "/CN=" + crt.Subject.CommonName - - n, err := fmt.Fprintf(f, "V\t%vZ\t\t%v\t%v.crt\t%v\n", - crt.NotAfter.UTC().Format("060102150405"), - serialOutput, - filename, - subject) - if err != nil { - return err - } - if n == 0 { - return fmt.Errorf("written 0 bytes in index file") - } - return nil -} - -// |-ca.crt -// |-crlnumber -// |-index.txt -// |-index.txt.attr -// |-serial -// |-issued/ -// |- name.crt -// |-private -// |- ca.key -// |- name.key -func GeneratePKIStructure(pkiroot string) error { - - for _, dir := range []string{"private", "issued"} { - err := os.Mkdir(filepath.Join(pkiroot, dir), 0755) - if err != nil { - return fmt.Errorf("creating dir %v: %v", dir, err) - } - } - - files := []struct { - Name string - Content string - File *os.File - }{ - {Name: "serial", Content: "01"}, - {Name: "crlnumber", Content: "01"}, - {Name: "index.txt", Content: ""}, - {Name: "index.txt.attr", Content: "unique_subject = no"}, - } - for _, f := range files { - // if using := here i get needs identifier, hm ?, needs to declare err before - var err error - f.File, err = os.Create(filepath.Join(pkiroot, f.Name)) - if err != nil { - return fmt.Errorf("create %v: %v", f.Name, err) - } - defer f.File.Close() - - if len(f.Content) == 0 { - continue - } - - n, err := fmt.Fprintln(f.File, f.Content) - if err != nil { - return fmt.Errorf("write %v: %v", f.Name, err) - } - if n == 0 { - return fmt.Errorf("write %v, written 0 bytes", f.Name) - } - } - - return nil -} - -func NextNumber(pkiroot, name string) (*big.Int, error) { - serial := big.NewInt(0) - - f, err := os.OpenFile(filepath.Join(pkiroot, name), os.O_RDWR, 0644) - if err != nil { - return nil, err - } - defer f.Close() - - n, err := fmt.Fscanf(f, "%X\n", serial) - if err != nil { - return nil, err - } - if n != 1 { - return nil, fmt.Errorf("supposed to read 1 element, read: %v", n) - } - - next := big.NewInt(1) - next.Add(serial, next) - output := fmt.Sprintf("%X", next) - // For compatibility with openssl we need an even length - if len(output)%2 == 1 { - output = "0" + output - } - f.Truncate(0) - f.Seek(0, 0) - - n, err = fmt.Fprintln(f, output) - if err != nil { - return nil, err - } - if n == 0 { - return nil, fmt.Errorf("supposed to write 1 element, written: %v", n) - } - - return serial, nil -} diff --git a/pkg/easypki/easyca_test.go b/pkg/easypki/easyca_test.go deleted file mode 100644 index ece603a..0000000 --- a/pkg/easypki/easyca_test.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2015 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package easypki - -import ( - "io/ioutil" - "math/big" - "math/rand" - "os" - "path/filepath" - "testing" - "time" -) - -func TestGeneratePKIStructure(t *testing.T) { - rand.Seed(time.Now().UnixNano()) - - pkiroot, err := ioutil.TempDir("", "gotestpki") - if err != nil { - t.Fatalf("failed to create temp dir: %v", err) - } - - if err := GeneratePKIStructure(pkiroot); err != nil { - t.Fatalf("%v", err) - } - - // We should check the minimum content also.. - toCheck := []struct { - Name string - Dir bool - Content string - }{ - {"private", true, ""}, - {"issued", true, ""}, - {"serial", false, "01\n"}, - {"crlnumber", false, "01\n"}, - {"index.txt", false, ""}, - {"index.txt.attr", false, "unique_subject = no\n"}, - } - - for _, name := range toCheck { - fd, err := os.Stat(filepath.Join(pkiroot, name.Name)) - if err != nil { - t.Errorf("%v: %v", name.Name, err) - } - if name.Dir && !fd.IsDir() { - t.Errorf("%v supposed to be a directory", name.Name) - } - if len(name.Content) > 0 { - f, err := os.Open(filepath.Join(pkiroot, name.Name)) - if err != nil { - t.Fatalf("failed open %v: %v", name.Name, err) - } - defer f.Close() - bytes, err := ioutil.ReadAll(f) - if err != nil { - t.Fatalf("failed read %v: %v", name.Name, err) - } - if string(bytes) != name.Content { - t.Fatalf("%v content expected %v, got: %v", name.Name, name.Content, string(bytes)) - } - } - } - if err := os.RemoveAll(pkiroot); err != nil { - t.Logf("failed cleaning tmp dir %v: %v", pkiroot, err) - } -} - -func TestNextNumber(t *testing.T) { - pkiroot, err := ioutil.TempDir("", "gotestpki") - if err != nil { - t.Fatalf("failed to create temp dir: %v", err) - } - - if err := GeneratePKIStructure(pkiroot); err != nil { - t.Fatalf("generate pki structure: %v", err) - } - - n, err := NextNumber(pkiroot, "serial") - if err != nil { - t.Fatal("failed get next serial number: %v", err) - } - if big.NewInt(1).Cmp(n) != 0 { - t.Fatalf("after init serial is supposed to be 1, value is: %v", n) - } - // File content is now 02 - f, err := os.Open(filepath.Join(pkiroot, "serial")) - if err != nil { - t.Fatalf("failed open serial: %v", err) - } - defer f.Close() - bytes, err := ioutil.ReadAll(f) - if err != nil { - t.Fatalf("failed read serial: %v", err) - } - if string(bytes) != "02\n" { - t.Fatalf("serial content expected 02, got: %v", string(bytes)) - } -} - -func TestLargeNextNumber(t *testing.T) { - pkiroot, err := ioutil.TempDir("", "gotestpki") - if err != nil { - t.Fatalf("failed to create temp dir: %v", err) - } - - if err := GeneratePKIStructure(pkiroot); err != nil { - t.Fatalf("generate pki structure: %v", err) - } - - for { - n, err := NextNumber(pkiroot, "serial") - if err != nil { - t.Fatal("failed get next serial number: %v", err) - } - if big.NewInt(255).Cmp(n) == 0 { - break - } - } - f, err := os.Open(filepath.Join(pkiroot, "serial")) - if err != nil { - t.Fatalf("failed open serial: %v", err) - } - defer f.Close() - bytes, err := ioutil.ReadAll(f) - if err != nil { - t.Fatalf("failed read serial: %v", err) - } - if string(bytes) != "0100\n" { - t.Fatalf("serial content expected 0100, got: %v", string(bytes)) - } -} diff --git a/pkg/easypki/easypki.go b/pkg/easypki/easypki.go new file mode 100644 index 0000000..be30748 --- /dev/null +++ b/pkg/easypki/easypki.go @@ -0,0 +1,147 @@ +// Copyright 2015 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package easypki provides helpers to manage a Public Key Infrastructure. +package easypki + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "errors" + "fmt" + "time" + + "github.com/google/easypki/pkg/certificate" + "github.com/google/easypki/pkg/store" +) + +const ( + defaultPrivateKeySize = 2048 +) + +// Signing errors. +var ( + ErrCannotSelfSignNonCA = errors.New("cannot self sign non CA request") + ErrMaxPathLenReached = errors.New("max path len reached") +) + +// Request is a struct for providing configuration to +// GenerateCertificate when actioning a certification generation request. +type Request struct { + Name string + IsClientCertificate bool + PrivateKeySize int + Template *x509.Certificate +} + +// EasyPKI wraps helpers to handle a Public Key Infrastructure. +type EasyPKI struct { + Store store.Store +} + +// GetCA fetches and returns the named Certificate Authrority bundle +// from the store. +func (e *EasyPKI) GetCA(name string) (*certificate.Bundle, error) { + return e.GetBundle(name, name) +} + +// GetBundle fetches and returns a certificate bundle from the store. +func (e *EasyPKI) GetBundle(caName, name string) (*certificate.Bundle, error) { + k, c, err := e.Store.Fetch(caName, name) + if err != nil { + return nil, fmt.Errorf("failed fetching bundle %v within CA %v: %v", name, caName, err) + } + + return certificate.RawToBundle(name, k, c) +} + +// Sign signs a generated certificate bundle based on the given request with +// the given signer. +func (e *EasyPKI) Sign(signer *certificate.Bundle, req *Request) error { + if !req.Template.IsCA && signer == nil { + return ErrCannotSelfSignNonCA + } + if req.Template.IsCA && signer != nil && signer.Cert.MaxPathLen == 0 { + return ErrMaxPathLenReached + } + + if req.PrivateKeySize == 0 { + req.PrivateKeySize = defaultPrivateKeySize + } + privateKey, err := rsa.GenerateKey(rand.Reader, req.PrivateKeySize) + if err != nil { + return fmt.Errorf("failed generating private key: %v", err) + } + publicKey := privateKey.Public() + + if err := defaultTemplate(req, publicKey); err != nil { + return fmt.Errorf("failed updating generation request: %v", err) + } + + if req.Template.IsCA { + var intermediateCA bool + if signer != nil { + intermediateCA = true + if signer.Cert.MaxPathLen > 0 { + req.Template.MaxPathLen = signer.Cert.MaxPathLen - 1 + } + } + if err := caTemplate(req, intermediateCA); err != nil { + return fmt.Errorf("failed updating generation request for CA: %v", err) + } + if !intermediateCA { + // Use the generated certificate template and private key (self-signing). + signer = &certificate.Bundle{Name: req.Name, Cert: req.Template, Key: privateKey} + } + } else { + nonCATemplate(req) + } + + rawCert, err := x509.CreateCertificate(rand.Reader, req.Template, signer.Cert, publicKey, signer.Key) + if err != nil { + return fmt.Errorf("failed creating and signing certificate: %v", err) + } + + if err := e.Store.Add(signer.Name, req.Name, req.Template.IsCA, x509.MarshalPKCS1PrivateKey(privateKey), rawCert); err != nil { + return fmt.Errorf("failed saving generated bundle: %v", err) + } + return nil +} + +// Revoke revokes the given certificate from the store. +func (e *EasyPKI) Revoke(caName string, cert *x509.Certificate) error { + if err := e.Store.Update(caName, cert.SerialNumber, certificate.Revoked); err != nil { + return fmt.Errorf("failed revoking certificate: %v", err) + } + return nil +} + +// CRL builds a CRL for a given CA based on the revoked certs. +func (e *EasyPKI) CRL(caName string, expire time.Time) ([]byte, error) { + revoked, err := e.Store.Revoked(caName) + if err != nil { + return nil, fmt.Errorf("failed retrieving revoked certificates for %v: %v", caName, err) + } + ca, err := e.GetCA(caName) + if err != nil { + return nil, fmt.Errorf("failed retrieving CA bundle %v: %v", caName, err) + } + + crl, err := ca.Cert.CreateCRL(rand.Reader, ca.Key, revoked, time.Now(), expire) + if err != nil { + return nil, fmt.Errorf("failed creating crl for %v: %v", caName, err) + } + return crl, nil +} diff --git a/pkg/easypki/easypki_test.go b/pkg/easypki/easypki_test.go new file mode 100644 index 0000000..3c839dc --- /dev/null +++ b/pkg/easypki/easypki_test.go @@ -0,0 +1,162 @@ +// Copyright 2015 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package easypki + +import ( + "crypto/x509" + "crypto/x509/pkix" + "io/ioutil" + "net" + "testing" + "time" + + "reflect" + + "github.com/google/easypki/pkg/store" +) + +func TestE2E(t *testing.T) { + root, err := ioutil.TempDir("", "testeasypki") + if err != nil { + t.Fatalf("failed creating temporary directory: %v", err) + } + //defer os.RemoveAll(root) + + pki := &EasyPKI{Store: &store.Local{Root: root}} + + commonSubject := pkix.Name{ + Organization: []string{"Acme Inc."}, + OrganizationalUnit: []string{"IT"}, + Locality: []string{"Agloe"}, + Country: []string{"US"}, + Province: []string{"New York"}, + } + + caRequest := &Request{ + Name: "Root_CA", + Template: &x509.Certificate{ + Subject: commonSubject, + NotAfter: time.Now().AddDate(0, 0, 30), + MaxPathLen: 1, + IsCA: true, + }, + } + caRequest.Template.Subject.CommonName = "Root CA" + if err := pki.Sign(nil, caRequest); err != nil { + t.Fatalf("Sign(nil, %v): got error: %v != expected nil", caRequest, err) + } + rootCA, err := pki.GetCA(caRequest.Name) + if err != nil { + t.Fatalf("GetCA(%v): got error %v != expect nil", caRequest.Name, err) + } + + cliRequest := &Request{ + Name: "bob@acme.org", + Template: &x509.Certificate{ + Subject: commonSubject, + NotAfter: time.Now().AddDate(0, 0, 30), + EmailAddresses: []string{"bob@acme.org"}, + }, + IsClientCertificate: true, + } + cliRequest.Template.Subject.CommonName = "bob@acme.org" + if err := pki.Sign(rootCA, cliRequest); err != nil { + t.Fatalf("Sign(%v, %v): go error: %v != expected nil", rootCA, cliRequest, err) + } + cli, err := pki.GetBundle(caRequest.Name, cliRequest.Name) + if err != nil { + t.Fatalf("GetBundle(%v, %v): go error %v != expected nil", caRequest.Name, cliRequest.Name, err) + } + + expectedExtKeyUsage := []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} + if !reflect.DeepEqual(cli.Cert.ExtKeyUsage, expectedExtKeyUsage) { + t.Errorf("Client ExtKeyUsage: got %v != expected %v", cli.Cert.ExtKeyUsage, expectedExtKeyUsage) + } + + if err := pki.Sign(nil, cliRequest); err != ErrCannotSelfSignNonCA { + t.Errorf("Sign(nil, %v): got error %v != expected %v", cliRequest, err, ErrCannotSelfSignNonCA) + } + + intRequest := &Request{ + Name: "Intermediate_CA", + Template: &x509.Certificate{ + Subject: commonSubject, + NotAfter: time.Now().AddDate(0, 0, 30), + IsCA: true, + }, + } + intRequest.Template.Subject.CommonName = "Intermediate CA" + if err := pki.Sign(rootCA, intRequest); err != nil { + t.Fatalf("Sign(%v, %v): go error: %v != expected nil", rootCA, intRequest, err) + } + intCA, err := pki.GetCA(intRequest.Name) + if err != nil { + t.Fatalf("GetCA(%v): got error %v != expect nil", intRequest.Name, err) + } + + srvRequest := &Request{ + Name: "wiki.acme.org", + Template: &x509.Certificate{ + Subject: commonSubject, + NotAfter: time.Now().AddDate(0, 0, 30), + DNSNames: []string{"wiki.acme.org"}, + IPAddresses: []net.IP{net.ParseIP("10.10.10.10")}, + }, + PrivateKeySize: 4096, + } + srvRequest.Template.Subject.CommonName = "wiki.acme.org" + if err := pki.Sign(intCA, srvRequest); err != nil { + t.Fatalf("Sign(%v, %v): go error: %v != expected nil", intCA, srvRequest, err) + + } + srv, err := pki.GetBundle(intRequest.Name, srvRequest.Name) + if err != nil { + t.Fatalf("GetBundle(%v, %v): go error %v != expected nil", intRequest.Name, srvRequest.Name, err) + } + + if err := pki.Revoke(intRequest.Name, srv.Cert); err != nil { + t.Fatalf("Revoke(%v, %v): got error: %v != expected nil", intRequest.Name, srv.Cert, err) + } + expire := time.Now().Add(time.Hour * 24) + crlBytes, err := pki.CRL(intRequest.Name, expire) + if err != nil { + t.Fatalf("CRL(%v, %v): got error %v != expected nil", intRequest.Name, expire, err) + } + + crl, err := x509.ParseCRL(crlBytes) + if err != nil { + t.Fatalf("ParseCRL(%v): got error %v != expected nil", crlBytes, err) + } + if len(crl.TBSCertList.RevokedCertificates) != 1 { + t.Fatalf("CRL does not have 1 revoked certificate: %v", crl) + } + if srv.Cert.SerialNumber.Cmp(crl.TBSCertList.RevokedCertificates[0].SerialNumber) != 0 { + t.Fatalf("Server certificate serial number %v != revoked server certificate serial %v", + srv.Cert.SerialNumber, crl.TBSCertList.RevokedCertificates[0].SerialNumber) + } + + tooDeepReq := &Request{ + Name: "Deep_Intermediate_CA", + Template: &x509.Certificate{ + Subject: commonSubject, + NotAfter: time.Now().AddDate(0, 0, 30), + IsCA: true, + }, + } + tooDeepReq.Template.Subject.CommonName = "Deep Intermediate CA" + if err := pki.Sign(intCA, tooDeepReq); err != ErrMaxPathLenReached { + t.Errorf("Sign(%v, %v): got error %v != expected %v", intCA, tooDeepReq, err, ErrMaxPathLenReached) + } +} diff --git a/pkg/easypki/template.go b/pkg/easypki/template.go new file mode 100644 index 0000000..0a87cdc --- /dev/null +++ b/pkg/easypki/template.go @@ -0,0 +1,74 @@ +// Copyright 2015 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package easypki + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "encoding/asn1" + "fmt" + "math/big" + "time" +) + +func defaultTemplate(genReq *Request, publicKey crypto.PublicKey) error { + publicKeyBytes, err := asn1.Marshal(*publicKey.(*rsa.PublicKey)) + if err != nil { + return fmt.Errorf("failed marshaling public key: %v", err) + } + subjectKeyID := sha1.Sum(publicKeyBytes) + genReq.Template.SubjectKeyId = subjectKeyID[:] + + // Random serial number. + snLimit := new(big.Int).Lsh(big.NewInt(1), 128) + sn, err := rand.Int(rand.Reader, snLimit) + if err != nil { + return fmt.Errorf("failed generating serial number: %s", err) + } + genReq.Template.SerialNumber = sn + + genReq.Template.NotBefore = time.Now() + genReq.Template.SignatureAlgorithm = x509.SHA256WithRSA + return nil +} + +func caTemplate(genReq *Request, intermediateCA bool) error { + genReq.Template.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageCRLSign + genReq.Template.BasicConstraintsValid = true + genReq.Template.MaxPathLenZero = true + + if intermediateCA { + return nil + } + + // Root certificate can self-sign. + genReq.Template.Issuer = genReq.Template.Subject + genReq.Template.AuthorityKeyId = genReq.Template.SubjectKeyId + return nil +} + +func nonCATemplate(genReq *Request) { + if !genReq.IsClientCertificate { + genReq.Template.ExtKeyUsage = append(genReq.Template.ExtKeyUsage, x509.ExtKeyUsageServerAuth) + } + // Clients can only use ClientAuth + genReq.Template.ExtKeyUsage = append(genReq.Template.ExtKeyUsage, x509.ExtKeyUsageClientAuth) + + // set the usage for non-CA certificates + genReq.Template.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment +} diff --git a/pkg/store/local.go b/pkg/store/local.go new file mode 100644 index 0000000..11208a1 --- /dev/null +++ b/pkg/store/local.go @@ -0,0 +1,383 @@ +// Copyright 2015 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package store + +import ( + "bufio" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "io/ioutil" + "math/big" + "os" + "path/filepath" + "regexp" + "strings" + "time" + + "crypto/x509" + + "github.com/google/easypki/pkg/certificate" +) + +// Predifined directory names. +const ( + LocalCertsDir = "certs" + LocalKeysDir = "keys" + LocalCrlsDir = "crls" +) + +var ( + // Index format + // 0 full string + // 1 Valid/Revoked/Expired + // 2 Expiration date + // 3 Revocation date + // 4 Serial + // 5 Filename + // 6 Subject + indexRegexp = regexp.MustCompile("^(V|R|E)\t([0-9]{12}Z)\t([0-9]{12}Z)?\t([0-9a-fA-F]{2,})\t([^\t]+)\t(.+)") +) + +// Local lets us store a Certificate Authority on the local filesystem. +// +// The structure used makes it compatible with openssl. +type Local struct { + Root string +} + +// path returns private and public key path. +func (l *Local) path(caName, name string) (priv string, cert string) { + priv = filepath.Join(l.Root, caName, LocalKeysDir, name+".key") + cert = filepath.Join(l.Root, caName, LocalCertsDir, name+".crt") + return +} + +// Exists checks if a certificate or private key already exist on the local +// filesystem for a given name. +func (l *Local) Exists(caName, name string) bool { + privPath, certPath := l.path(caName, name) + if _, err := os.Stat(privPath); err == nil { + return true + } + if _, err := os.Stat(certPath); err == nil { + return true + } + return false +} + +// Fetch fetchs the private key and certificate for a given name signed by caName. +func (l *Local) Fetch(caName, name string) ([]byte, []byte, error) { + filepath.Join(l.Root, caName) + + keyPath, certPath := l.path(caName, name) + k, err := readPEM(keyPath) + if err != nil { + return nil, nil, fmt.Errorf("failed reading CA private key from file %v: %v", keyPath, err) + } + c, err := readPEM(certPath) + if err != nil { + return nil, nil, fmt.Errorf("failed reading CA cert from file %v: %v", certPath, err) + } + return k, c, nil +} + +func readPEM(path string) ([]byte, error) { + bytes, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed reading %v: %v", path, err) + } + p, _ := pem.Decode(bytes) + if p == nil { + return nil, fmt.Errorf("no PEM data found for certificate") + } + return p.Bytes, nil +} + +// Add adds the given bundle to the local filesystem. +func (l *Local) Add(caName, name string, isCa bool, key, cert []byte) error { + if l.Exists(caName, name) { + return fmt.Errorf("a bundle already exists for the name %v within CA %v", name, caName) + } + if err := l.writeBundle(caName, name, isCa, key, cert); err != nil { + return fmt.Errorf("failed writing bundle %v within CA %v to the local filesystem: %v", name, caName, err) + } + if err := l.updateIndex(caName, name, cert); err != nil { + return fmt.Errorf("failed updating CA %v index: %v", caName, err) + } + return nil +} + +// writeBundle encodes in PEM format the bundle private key and +// certificate and stores them on the local filesystem. +func (l *Local) writeBundle(caName, name string, isCa bool, key, cert []byte) error { + caDir := filepath.Join(l.Root, caName) + if _, err := os.Stat(caDir); err != nil { + if err := InitCADir(caDir); err != nil { + return fmt.Errorf("root directory for CA %v does not exist and cannot be created: %v", caDir, err) + } + } + keyPath, certPath := l.path(caName, name) + if err := encodeAndWrite(keyPath, "RSA PRIVATE KEY", key); err != nil { + return fmt.Errorf("failed encoding and writing private key file: %v", err) + } + if err := encodeAndWrite(certPath, "CERTIFICATE", cert); err != nil { + return fmt.Errorf("failed encoding and writing cert file: %v", err) + } + + if isCa && name != caName { + intCaDir := filepath.Join(l.Root, name) + if err := InitCADir(intCaDir); err != nil { + return fmt.Errorf("root directory for CA %v does not exist and cannot be created: %v", intCaDir, err) + } + kp, cp := l.path(name, name) + if err := os.Link(keyPath, kp); err != nil { + return fmt.Errorf("failed creating hard link from %v to %v: %v", keyPath, kp, err) + } + if err := os.Link(certPath, cp); err != nil { + return fmt.Errorf("failed creating hard link from %v to %v: %v", certPath, cp, err) + } + } + return nil +} + +func encodeAndWrite(path, pemType string, data []byte) error { + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + + return pem.Encode(f, &pem.Block{ + Type: pemType, + Bytes: data, + }) +} + +// updateIndex appends a line to the index.txt with few information about the +// given the certificate. +func (l *Local) updateIndex(caName, name string, rawCert []byte) error { + f, err := os.OpenFile(filepath.Join(l.Root, caName, "index.txt"), os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return err + } + defer f.Close() + + cert, err := x509.ParseCertificate(rawCert) + if err != nil { + return fmt.Errorf("failed parsing raw certificate %v: %v", name, err) + } + + sn := fmt.Sprintf("%X", cert.SerialNumber) + // For compatibility with openssl we need an even length. + if len(sn)%2 == 1 { + sn = "0" + sn + } + + // Date format: yymmddHHMMSSZ + // E|R|V<tab>Expiry<tab>[RevocationDate]<tab>Serial<tab>filename<tab>SubjectDN + var subject string + if strs := cert.Subject.Country; len(strs) == 1 { + subject += "/C=" + strs[0] + } + if strs := cert.Subject.Organization; len(strs) == 1 { + subject += "/O=" + strs[0] + } + if strs := cert.Subject.OrganizationalUnit; len(strs) == 1 { + subject += "/OU=" + strs[0] + } + if strs := cert.Subject.Locality; len(strs) == 1 { + subject += "/L=" + strs[0] + } + if strs := cert.Subject.Province; len(strs) == 1 { + subject += "/ST=" + strs[0] + } + subject += "/CN=" + cert.Subject.CommonName + + n, err := fmt.Fprintf(f, "V\t%vZ\t\t%v\t%v.crt\t%v\n", + cert.NotAfter.UTC().Format("060102150405"), + sn, + name, + subject) + if err != nil { + return err + } + if n == 0 { + return fmt.Errorf("written 0 bytes in index file") + } + return nil +} + +// Update updates the state of a given certificate in the index.txt. +func (l *Local) Update(caName string, sn *big.Int, st certificate.State) error { + f, err := os.OpenFile(filepath.Join(l.Root, caName, "index.txt"), os.O_RDWR, 0644) + if err != nil { + return err + } + defer f.Close() + + var state string + switch st { + case certificate.Valid: + state = "V" + case certificate.Revoked: + state = "R" + case certificate.Expired: + state = "E" + default: + return fmt.Errorf("unhandled certificate state: %v", st) + } + + var lines []string + scanner := bufio.NewScanner(f) + for scanner.Scan() { + matches := indexRegexp.FindStringSubmatch(scanner.Text()) + if len(matches) != 7 { + return fmt.Errorf("line [%v] is incorrectly formated", scanner.Text()) + } + + matchedSerial := big.NewInt(0) + fmt.Sscanf(matches[4], "%X", matchedSerial) + if matchedSerial.Cmp(sn) == 0 { + if matches[1] == state { + return nil + } + + lines = append(lines, fmt.Sprintf("%v\t%v\t%vZ\t%v\t%v\t%v", + state, + matches[2], + time.Now().UTC().Format("060102150405"), + matches[4], + matches[5], + matches[6])) + } else { + lines = append(lines, matches[0]) + } + } + + f.Truncate(0) + f.Seek(0, 0) + + for _, line := range lines { + n, err := fmt.Fprintln(f, line) + if err != nil { + return fmt.Errorf("failed writing line [%v]: %v", line, err) + } + if n == 0 { + return fmt.Errorf("failed writing line [%v]: written 0 bytes", line) + } + } + return nil +} + +// Revoked returns a list of revoked certificates. +func (l *Local) Revoked(caName string) ([]pkix.RevokedCertificate, error) { + index, err := os.Open(filepath.Join(l.Root, caName, "index.txt")) + if err != nil { + return nil, err + } + defer index.Close() + + var revokedCerts []pkix.RevokedCertificate + scanner := bufio.NewScanner(index) + for scanner.Scan() { + matches := indexRegexp.FindStringSubmatch(scanner.Text()) + if len(matches) != 7 { + return nil, fmt.Errorf("line [%v] is incorrectly formated", scanner.Text()) + } + if matches[1] != "R" { + continue + } + + sn := big.NewInt(0) + fmt.Sscanf(matches[4], "%X", sn) + t, err := time.Parse("060102150405", strings.TrimSuffix(matches[3], "Z")) + if err != nil { + return nil, fmt.Errorf("failed parsing revocation time %v: %v", matches[3], err) + } + revokedCerts = append(revokedCerts, pkix.RevokedCertificate{ + SerialNumber: sn, + RevocationTime: t, + }) + } + return revokedCerts, nil +} + +// InitCADir creates the basic structure of a CA subdirectory. +// +// |- crlnumber +// |- index.txt +// |- index.txt.attr +// |- serial +// |- certs/ +// |- ca.crt +// |- name.crt +// |- keys/ +// |- ca.key +// |- name.key +func InitCADir(path string) error { + if _, err := os.Stat(path); err == nil { + return nil + } + if err := os.Mkdir(path, 0755); err != nil { + return fmt.Errorf("failed creating CA root directory %v: %v", path, err) + } + dirs := map[string]os.FileMode{ + filepath.Join(path, LocalCrlsDir): 0700, + filepath.Join(path, LocalCertsDir): 0755, + filepath.Join(path, LocalKeysDir): 0700, + } + for d, m := range dirs { + if err := os.Mkdir(d, m); err != nil { + return fmt.Errorf("failed creating directory %v: %v", d, err) + } + } + + files := []struct { + Name string + Content string + }{ + {Name: "serial", Content: "01"}, + {Name: "crlnumber", Content: "01"}, + {Name: "index.txt", Content: ""}, + {Name: "index.txt.attr", Content: "unique_subject = no"}, + } + for _, f := range files { + err := func(path, content string) error { + fh, err := os.Create(path) + if err != nil { + return fmt.Errorf("failed creating file %v: %v", path, err) + } + defer fh.Close() + + if content == "" { + return nil + } + + n, err := fmt.Fprintln(fh, content) + if err != nil { + return fmt.Errorf("failed wrinting %v in %v: %v", content, path, err) + } + if n == 0 { + return fmt.Errorf("failed writing %v in %v: 0 bytes written", content, path) + } + return nil + }(filepath.Join(path, f.Name), f.Content) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/store/store.go b/pkg/store/store.go new file mode 100644 index 0000000..2b311d9 --- /dev/null +++ b/pkg/store/store.go @@ -0,0 +1,65 @@ +// Copyright 2015 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package store provides different methods to store a Public Key Infrastructure. +package store + +import ( + "crypto/x509/pkix" + "math/big" + + "github.com/google/easypki/pkg/certificate" +) + +// Store reprents a way to store a Certificate Authority. +type Store interface { + // Add adds a newly signed certificate bundle to the store. + // + // Args: + // The CA name, if the certificate was signed with an intermediate CA. + // The certificate bundle name. + // Is the bundle to add an intermediate CA. + // The raw private key. + // The raw certificate. + // + // Returns an error if it failed to store the bundle. + Add(string, string, bool, []byte, []byte) error + + // Fetch fetches a certificate bundle from the store. + // + // Args: + // The CA name, if the certificate was signed with an intermediate CA. + // The name of the certificate bundle. + // + // Returns the raw private key and certificate respectively or an error. + Fetch(string, string) ([]byte, []byte, error) + + // Update updates the state of a certificate. (Valid, Revoked, Expired) + // + // Args: + // The CA name, if the certificate was signed with an intermediate CA. + // The serial of the certificate to update. + // The new state. + // + // Returns an error if the update failed. + Update(string, *big.Int, certificate.State) error + + // Revoked returns a list of revoked certificates for a given CA. + // + // Args: + // The CA name, if it is for an intermediate CA. + // + // Returns a list of revoked certificate or an error. + Revoked(string) ([]pkix.RevokedCertificate, error) +} |
