Merge pull request #65 from letsencrypt/vendorize

Vendorize all dependencies with godep.
This commit is contained in:
jsha 2015-03-26 19:12:15 -07:00
commit e077548886
211 changed files with 189571 additions and 67 deletions

3
.gitignore vendored
View File

@ -11,6 +11,9 @@ _test
*.[568vq] *.[568vq]
[568vq].out [568vq].out
# Vim swap files
*.sw?
*.cgo1.go *.cgo1.go
*.cgo2.c *.cgo2.c
_cgo_defun.c _cgo_defun.c

View File

@ -13,21 +13,5 @@ before_install:
- go get github.com/mattn/goveralls - go get github.com/mattn/goveralls
- go get github.com/modocache/gover - go get github.com/modocache/gover
install:
- go get -t -v -tags "pkcs11" ./...
script: script:
- go vet -x ./... - bash test.sh
- $HOME/gopath/bin/golint ./...
- go test -covermode=count -coverprofile=analysis.coverprofile ./analysis/
- go test -covermode=count -coverprofile=ca.coverprofile ./ca/
- go test -covermode=count -coverprofile=core.coverprofile ./core/
- go test -covermode=count -coverprofile=log.coverprofile ./log/
- go test -covermode=count -coverprofile=ra.coverprofile ./ra/
- go test -covermode=count -coverprofile=rpc.coverprofile ./rpc/
- go test -covermode=count -coverprofile=sa.coverprofile ./sa/
- go test -covermode=count -coverprofile=test.coverprofile ./test/
- go test -covermode=count -coverprofile=va.coverprofile ./va/
- go test -covermode=count -coverprofile=wfe.coverprofile ./wfe/
- $HOME/gopath/bin/gover
- $HOME/gopath/bin/goveralls -coverprofile=gover.coverprofile -service=travis-ci

View File

@ -8,16 +8,6 @@ EXPOSE 4000
# Assume the configuration is in /etc/boulder # Assume the configuration is in /etc/boulder
ENV BOULDER_CONFIG=/boulder/config.json ENV BOULDER_CONFIG=/boulder/config.json
# Load the dependencies
RUN go-wrapper download github.com/bifurcation/gose && \
go-wrapper download github.com/codegangsta/cli && \
go-wrapper download github.com/streadway/amqp && \
go-wrapper download github.com/mattn/go-sqlite3 && \
go-wrapper download github.com/go-sql-driver/mysql && \
go-wrapper download github.com/cloudflare/cfssl/auth && \
go-wrapper download github.com/cloudflare/cfssl/config && \
go-wrapper download github.com/cloudflare/cfssl/signer
# Copy in the Boulder sources # Copy in the Boulder sources
RUN mkdir -p /go/src/github.com/letsencrypt/boulder RUN mkdir -p /go/src/github.com/letsencrypt/boulder
COPY . /go/src/github.com/letsencrypt/boulder COPY . /go/src/github.com/letsencrypt/boulder

59
Godeps/Godeps.json generated Normal file
View File

@ -0,0 +1,59 @@
{
"ImportPath": "github.com/letsencrypt/boulder",
"GoVersion": "go1.4.1",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/cloudflare/cfssl/api",
"Rev": "1415724f395ffd7aa29176066765cabc68193453"
},
{
"ImportPath": "github.com/cloudflare/cfssl/auth",
"Rev": "1415724f395ffd7aa29176066765cabc68193453"
},
{
"ImportPath": "github.com/cloudflare/cfssl/config",
"Rev": "1415724f395ffd7aa29176066765cabc68193453"
},
{
"ImportPath": "github.com/cloudflare/cfssl/csr",
"Rev": "1415724f395ffd7aa29176066765cabc68193453"
},
{
"ImportPath": "github.com/cloudflare/cfssl/errors",
"Rev": "1415724f395ffd7aa29176066765cabc68193453"
},
{
"ImportPath": "github.com/cloudflare/cfssl/helpers",
"Rev": "1415724f395ffd7aa29176066765cabc68193453"
},
{
"ImportPath": "github.com/cloudflare/cfssl/log",
"Rev": "1415724f395ffd7aa29176066765cabc68193453"
},
{
"ImportPath": "github.com/cloudflare/cfssl/signer",
"Rev": "1415724f395ffd7aa29176066765cabc68193453"
},
{
"ImportPath": "github.com/codegangsta/cli",
"Comment": "1.2.0-64-ge1712f3",
"Rev": "e1712f381785e32046927f64a7c86fe569203196"
},
{
"ImportPath": "github.com/go-sql-driver/mysql",
"Comment": "v1.2-88-ga197e5d",
"Rev": "a197e5d40516f2e9f74dcee085a5f2d4604e94df"
},
{
"ImportPath": "github.com/mattn/go-sqlite3",
"Rev": "308067797b0fcce4ca06362580dc6db77c1bfeda"
},
{
"ImportPath": "github.com/streadway/amqp",
"Rev": "150b7f24d6ad507e6026c13d85ce1f1391ac7400"
}
]
}

5
Godeps/Readme generated Normal file
View File

@ -0,0 +1,5 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

2
Godeps/_workspace/.gitignore generated vendored Normal file
View File

@ -0,0 +1,2 @@
/pkg
/bin

View File

@ -0,0 +1,204 @@
// Package api implements an HTTP-based API and server for CF-SSL.
package api
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/log"
)
// Handler is an interface providing a generic mechanism for handling HTTP requests.
type Handler interface {
Handle(w http.ResponseWriter, r *http.Request) error
}
// HTTPHandler is a wrapper that encapsulates Handler interface as http.Handler.
// HttpHandler also enforces that the Handler only responds to requests with registered HTTP method.
type HTTPHandler struct {
Handler // CFSSL handler
Method string // The assoicated HTTP method
}
// HandlerFunc is similar to the http.HandlerFunc type; it serves as
// an adapter allowing the use of ordinary functions as Handlers. If
// f is a function with the appropriate signature, HandlerFunc(f) is a
// Handler object that calls f.
type HandlerFunc func(http.ResponseWriter, *http.Request) error
// Handle calls f(w, r)
func (f HandlerFunc) Handle(w http.ResponseWriter, r *http.Request) error {
return f(w, r)
}
// handleError is the centralised error handling and reporting.
func handleError(w http.ResponseWriter, err error) (code int) {
if err == nil {
return http.StatusOK
}
msg := err.Error()
httpCode := http.StatusInternalServerError
// If it is recognized as HttpError emitted from cf-ssl,
// we rewrite the status code accordingly. If it is a
// cf-ssl error, set the http status to StatusBadRequest
switch err := err.(type) {
case *errors.HTTPError:
httpCode = err.StatusCode
code = err.StatusCode
case *errors.Error:
httpCode = http.StatusBadRequest
code = err.ErrorCode
msg = err.Message
}
response := NewErrorResponse(msg, code)
jsonMessage, err := json.Marshal(response)
if err != nil {
log.Errorf("Failed to marshal JSON: %v", err)
} else {
msg = string(jsonMessage)
}
http.Error(w, msg, httpCode)
return code
}
// ServeHTTP encapsulates the call to underlying Handler to handle the request
// and return the response with proper HTTP status code
func (h HTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var err error
// Throw 405 when requested with an unsupported verb.
if r.Method != h.Method {
err = errors.NewMethodNotAllowed(r.Method)
} else {
err = h.Handle(w, r)
}
status := handleError(w, err)
log.Infof("%s - \"%s %s\" %d", r.RemoteAddr, r.Method, r.URL, status)
}
// readRequestBlob takes a JSON-blob-encoded response body in the form
// map[string]string and returns it, the list of keywords presented,
// and any error that occurred.
func readRequestBlob(r *http.Request) (map[string]string, error) {
var blob map[string]string
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, err
}
r.Body.Close()
err = json.Unmarshal(body, &blob)
if err != nil {
return nil, err
}
return blob, nil
}
// ProcessRequestOneOf reads a JSON blob for the request and makes
// sure it contains one of a set of keywords. For example, a request
// might have the ('foo' && 'bar') keys, OR it might have the 'baz'
// key. In either case, we want to accept the request; however, if
// none of these sets shows up, the request is a bad request, and it
// should be returned.
func ProcessRequestOneOf(r *http.Request, keywordSets [][]string) (map[string]string, []string, error) {
blob, err := readRequestBlob(r)
if err != nil {
return nil, nil, err
}
var matched []string
for _, set := range keywordSets {
if matchKeywords(blob, set) {
if matched != nil {
return nil, nil, errors.NewBadRequestString("mismatched parameters")
}
matched = set
}
}
if matched == nil {
return nil, nil, errors.NewBadRequestString("no valid parameter sets found")
}
return blob, matched, nil
}
// ProcessRequestFirstMatchOf reads a JSON blob for the request and returns
// the first match of a set of keywords. For example, a request
// might have one of the following combinations: (foo=1, bar=2), (foo=1), and (bar=2)
// By giving a specific ordering of those combinations, we could decide how to accept
// the request.
func ProcessRequestFirstMatchOf(r *http.Request, keywordSets [][]string) (map[string]string, []string, error) {
blob, err := readRequestBlob(r)
if err != nil {
return nil, nil, err
}
for _, set := range keywordSets {
if matchKeywords(blob, set) {
return blob, set, nil
}
}
return nil, nil, errors.NewBadRequestString("no valid parameter sets found")
}
func matchKeywords(blob map[string]string, keywords []string) bool {
for _, keyword := range keywords {
if _, ok := blob[keyword]; !ok {
return false
}
}
return true
}
// ResponseMessage implements the standard for response errors and
// messages. A message has a code and a string message.
type ResponseMessage struct {
Code int `json:"code"`
Message string `json:"message"`
}
// Response implements the CloudFlare standard for API
// responses. CFSSL does not currently use the messages field, but it
// is provided for compatability.
type Response struct {
Success bool `json:"success"`
Result interface{} `json:"result"`
Errors []ResponseMessage `json:"errors"`
Messages []ResponseMessage `json:"messages"`
}
// NewSuccessResponse is a shortcut for creating new successul API
// responses. CFSSL does not use the messages field, but it is
// provided to conform to the CloudFlare standard.
func NewSuccessResponse(result interface{}) Response {
return Response{
Success: true,
Result: result,
Errors: []ResponseMessage{},
Messages: []ResponseMessage{},
}
}
// NewErrorResponse is a shortcut for creating an error response for a
// single error.
func NewErrorResponse(message string, code int) Response {
return Response{
Success: false,
Result: nil,
Errors: []ResponseMessage{{code, message}},
Messages: []ResponseMessage{},
}
}
// SendResponse builds a response from the result, sets the JSON
// header, and writes to the http.ResponseWriter.
func SendResponse(w http.ResponseWriter, result interface{}) error {
response := NewSuccessResponse(result)
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
err := enc.Encode(response)
return err
}

View File

@ -0,0 +1,220 @@
package api
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
const (
ty = "Thank you!"
deny = "That's not true!"
)
func simpleHandle(w http.ResponseWriter, r *http.Request) error {
_, _, err := ProcessRequestOneOf(r, [][]string{
[]string{"compliment"},
[]string{"critique"},
})
if err != nil {
return err
}
return SendResponse(w, ty)
}
func cleverHandle(w http.ResponseWriter, r *http.Request) error {
_, matched, err := ProcessRequestFirstMatchOf(r, [][]string{
[]string{"compliment"},
[]string{"critique"},
})
if err != nil {
return err
}
if matched[0] == "critique" {
return SendResponse(w, deny)
}
return SendResponse(w, ty)
}
func post(t *testing.T, obj map[string]interface{}, ts *httptest.Server) (resp *http.Response, body []byte) {
blob, err := json.Marshal(obj)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
func get(t *testing.T, ts *httptest.Server) (resp *http.Response, body []byte) {
resp, err := http.Get(ts.URL)
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
func TestRigidHandle(t *testing.T) {
ts := httptest.NewServer(HTTPHandler{Handler: HandlerFunc(simpleHandle), Method: "POST"})
defer ts.Close()
// Response to compliment
obj := map[string]interface{}{}
obj["compliment"] = "it's good"
resp, body := post(t, obj, ts)
if resp.StatusCode != http.StatusOK {
t.Errorf("Test expected 200, have %d", resp.StatusCode)
}
message := new(Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Errorf("failed to read response body: %v", err)
t.Fatal("returned:", message)
}
if message.Result != ty {
t.Fatal("Wrong response")
}
// Response to critique
obj = map[string]interface{}{}
obj["critique"] = "it's bad"
resp, body = post(t, obj, ts)
if resp.StatusCode != http.StatusOK {
t.Errorf("Test expected 200, have %d", resp.StatusCode)
}
message = new(Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Errorf("failed to read response body: %v", err)
t.Fatal("returned:", message)
}
if message.Result != ty {
t.Fatal("Wrong response")
}
// reject mixed review
obj = map[string]interface{}{}
obj["critique"] = "it's OK"
obj["compliment"] = "it's not bad"
resp, body = post(t, obj, ts)
if resp.StatusCode != http.StatusBadRequest {
t.Errorf("Test expected 400, have %d", resp.StatusCode)
}
// reject empty review
obj = map[string]interface{}{}
resp, body = post(t, obj, ts)
if resp.StatusCode != http.StatusBadRequest {
t.Errorf("Test expected 400, have %d", resp.StatusCode)
}
// reject GET
resp, body = get(t, ts)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("Test expected 405, have %d", resp.StatusCode)
}
}
func TestCleverHandle(t *testing.T) {
ts := httptest.NewServer(HTTPHandler{Handler: HandlerFunc(cleverHandle), Method: "POST"})
defer ts.Close()
// Response ty to compliment
obj := map[string]interface{}{}
obj["compliment"] = "it's good"
resp, body := post(t, obj, ts)
if resp.StatusCode != http.StatusOK {
t.Errorf("Test expected 200, have %d", resp.StatusCode)
}
message := new(Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Errorf("failed to read response body: %v", err)
t.Fatal("returned:", message)
}
if message.Result != ty {
t.Fatal("Wrong response")
}
// Response deny to critique
obj = map[string]interface{}{}
obj["critique"] = "it's bad"
resp, body = post(t, obj, ts)
if resp.StatusCode != http.StatusOK {
t.Errorf("Test expected 200, have %d", resp.StatusCode)
}
message = new(Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Errorf("failed to read response body: %v", err)
t.Fatal("returned:", message)
}
if message.Result != deny {
t.Fatal("Wrong response")
}
// Be polite to mixed review
obj = map[string]interface{}{}
obj["critique"] = "it's OK"
obj["compliment"] = "it's not bad"
resp, body = post(t, obj, ts)
message = new(Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Errorf("failed to read response body: %v", err)
t.Fatal("returned:", message)
}
if message.Result != ty {
t.Fatal("Wrong response")
}
// reject empty review
obj = map[string]interface{}{}
resp, body = post(t, obj, ts)
if resp.StatusCode != http.StatusBadRequest {
t.Errorf("Test expected 400, have %d", resp.StatusCode)
}
// reject GET
resp, body = get(t, ts)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("Test expected 405, have %d", resp.StatusCode)
}
}

View File

@ -0,0 +1,90 @@
package bundle
import (
"net/http"
"github.com/cloudflare/cfssl/bundler"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/api"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/log"
)
// Handler accepts requests for either remote or uploaded
// certificates to be bundled, and returns a certificate bundle (or
// error).
type Handler struct {
bundler *bundler.Bundler
}
// NewHandler creates a new bundler that uses the root bundle and
// intermediate bundle in the trust chain.
func NewHandler(caBundleFile, intBundleFile string) (http.Handler, error) {
var err error
b := new(Handler)
if b.bundler, err = bundler.NewBundler(caBundleFile, intBundleFile); err != nil {
return nil, err
}
log.Info("bundler API ready")
return api.HTTPHandler{Handler: b, Method: "POST"}, nil
}
// Handle implements an http.Handler interface for the bundle handler.
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
blob, matched, err := api.ProcessRequestFirstMatchOf(r,
[][]string{
[]string{"certificate"},
[]string{"domain"},
})
if err != nil {
log.Warningf("invalid request: %v", err)
return err
}
flavor := blob["flavor"]
bf := bundler.Ubiquitous
if flavor != "" {
bf = bundler.BundleFlavor(flavor)
}
log.Infof("request for flavor %v", bf)
var result *bundler.Bundle
switch matched[0] {
case "domain":
bundle, err := h.bundler.BundleFromRemote(blob["domain"], blob["ip"], bf)
if err != nil {
log.Warningf("couldn't bundle from remote: %v", err)
return err
}
result = bundle
case "certificate":
bundle, err := h.bundler.BundleFromPEM([]byte(blob["certificate"]), []byte(blob["private_key"]), bf)
if err != nil {
log.Warning("bad PEM certifcate or private key")
return err
}
serverName := blob["domain"]
ip := blob["ip"]
if serverName != "" {
err := bundle.Cert.VerifyHostname(serverName)
if err != nil {
return errors.Wrap(errors.CertificateError, errors.VerifyFailed, err)
}
}
if ip != "" {
err := bundle.Cert.VerifyHostname(ip)
if err != nil {
return errors.Wrap(errors.CertificateError, errors.VerifyFailed, err)
}
}
result = bundle
}
log.Info("wrote response")
return api.SendResponse(w, result)
}

View File

@ -0,0 +1,214 @@
package bundle
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/api"
)
const (
testCaBundleFile = "../testdata/ca-bundle.pem"
testIntBundleFile = "../testdata/int-bundle.pem"
testLeafCertFile = "../testdata/leaf.pem"
testLeafKeyFile = "../testdata/leaf.key"
testLeafWrongKeyFile = "../testdata/leaf.badkey"
testBrokenCertFile = "../testdata/broken.pem"
)
func newTestHandler(t *testing.T) (h http.Handler) {
h, err := NewHandler(testCaBundleFile, testIntBundleFile)
if err != nil {
t.Fatal(err)
}
return
}
func newBundleServer(t *testing.T) *httptest.Server {
ts := httptest.NewServer(newTestHandler(t))
return ts
}
func testBundleFile(t *testing.T, domain, ip, certFile, keyFile, flavor string) (resp *http.Response, body []byte) {
ts := newBundleServer(t)
defer ts.Close()
var certPEM, keyPEM []byte
if certFile != "" {
var err error
certPEM, err = ioutil.ReadFile(certFile)
if err != nil {
t.Fatal(err)
}
}
if keyFile != "" {
var err error
keyPEM, err = ioutil.ReadFile(keyFile)
if err != nil {
t.Fatal(err)
}
}
obj := map[string]string{"flavor": flavor}
if len(domain) > 0 {
obj["domain"] = domain
}
if len(ip) > 0 {
obj["ip"] = ip
}
if len(certPEM) > 0 {
obj["certificate"] = string(certPEM)
}
if len(keyPEM) > 0 {
obj["private_key"] = string(keyPEM)
}
blob, err := json.Marshal(obj)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
func TestNewHandler(t *testing.T) {
newTestHandler(t)
}
type bundleTest struct {
Domain string
IP string
CertFile string
KeyFile string
Flavor string
ExpectedHTTPStatus int
ExpectedSuccess bool
ExpectedErrorCode int
}
var bundleTests = []bundleTest{
// Test bundling with certificate
{
CertFile: testLeafCertFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertFile: testLeafCertFile,
Flavor: "ubiquitous",
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertFile: testLeafCertFile,
Flavor: "optimal",
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertFile: testLeafCertFile,
KeyFile: testLeafKeyFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertFile: testLeafCertFile,
Domain: "cfssl-leaf.com",
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
// Test bundling with remote domain
{
Domain: "google.com",
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: 1220,
},
// Error testing.
{
CertFile: testLeafCertFile,
KeyFile: testLeafWrongKeyFile,
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: 2300,
},
{
// no input parameter is specified
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: http.StatusBadRequest,
},
{
CertFile: testBrokenCertFile,
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: 1003,
},
{
CertFile: testLeafKeyFile,
KeyFile: testLeafKeyFile,
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: 1003,
},
{
CertFile: testLeafCertFile,
KeyFile: testLeafCertFile,
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: 2003,
},
{
CertFile: testLeafCertFile,
Domain: "cloudflare-leaf.com",
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: 1200,
},
}
func TestBundle(t *testing.T) {
for i, test := range bundleTests {
resp, body := testBundleFile(t, test.Domain, test.IP, test.CertFile, test.KeyFile, test.Flavor)
if resp.StatusCode != test.ExpectedHTTPStatus {
t.Errorf("Test %d: expected: %d, have %d", i, test.ExpectedHTTPStatus, resp.StatusCode)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}
message := new(api.Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Errorf("failed to read response body: %v", err)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess != message.Success {
t.Errorf("Test %d: expected: %v, have %v", i, test.ExpectedSuccess, message.Success)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess == true {
continue
}
if test.ExpectedErrorCode != message.Errors[0].Code {
t.Errorf("Test %d: expected: %v, have %v", i, test.ExpectedErrorCode, message.Errors[0].Code)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
}
}

View File

@ -0,0 +1,17 @@
package client
// SignResult is the result of signing a CSR.
type SignResult struct {
Certificate []byte `json:"certificate"`
}
// InfoReq is the request struct for an info API request.
type InfoReq struct {
Label string `json:"label"`
Profile string `json:"profile"`
}
// InfoResp is the response for an Info API request.
type InfoResp struct {
Certificate string `json:"certificate"`
}

View File

@ -0,0 +1,170 @@
package client
import (
"bytes"
"encoding/json"
stderr "errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"strconv"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/api"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/auth"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
)
type // A Server points to a remote CFSSL instance.
Server struct {
Address string
Port int
}
// NewServer sets up a new server target. The address should be the
// DNS name (or "name:port") of the remote CFSSL instance. If no port
// is specified, the CFSSL default port (8888) is used.
func NewServer(addr string) *Server {
host, port, err := net.SplitHostPort(addr)
if err != nil {
host, port, err = net.SplitHostPort(addr + ":8888")
if err != nil {
return nil
}
}
var portno int
if port == "" {
portno = 8888
} else {
portno, err = strconv.Atoi(port)
if err != nil {
return nil
}
}
return &Server{host, portno}
}
func (srv *Server) getURL(endpoint string) string {
return fmt.Sprintf("http://%s:%d/api/v1/cfssl/%s", srv.Address, srv.Port, endpoint)
}
// post connects to the remote server and returns a Response struct
func (srv *Server) post(url string, jsonData []byte) (*api.Response, error) {
buf := bytes.NewBuffer(jsonData)
resp, err := http.Post(url, "application/json", buf)
if err != nil {
return nil, errors.Wrap(errors.APIClientError, errors.ClientHTTPError, err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(errors.APIClientError, errors.IOError, err)
}
resp.Body.Close()
var response api.Response
err = json.Unmarshal(body, &response)
if err != nil {
return nil, errors.Wrap(errors.APIClientError, errors.JSONError, err)
}
if !response.Success || response.Result == nil {
if len(response.Errors) > 0 {
return nil, errors.Wrap(errors.APIClientError, errors.ServerRequestFailed, stderr.New(response.Errors[0].Message))
}
return nil, errors.New(errors.APIClientError, errors.ServerRequestFailed)
}
return &response, nil
}
// AuthSign fills out an authenticated signing request to the server,
// receiving a certificate or error in response.
// It takes the serialized JSON request to send, remote address and
// authentication provider.
func (srv *Server) AuthSign(req, id []byte, provider auth.Provider) ([]byte, error) {
return srv.AuthReq(req, id, provider, "sign")
}
// AuthInfo fills out an authenticated info request to the server,
// receiving a certificate or error in response.
// It takes the serialized JSON request to send, remote address and
// authentication provider.
func (srv *Server) AuthInfo(req, id []byte, provider auth.Provider) ([]byte, error) {
return srv.AuthReq(req, id, provider, "info")
}
// AuthReq is the common logic for AuthSign and AuthInfo -- perform the given
// request, and return the resultant certificate.
// The target is either 'sign' or 'info'.
func (srv *Server) AuthReq(req, ID []byte, provider auth.Provider, target string) ([]byte, error) {
url := srv.getURL("auth" + target)
token, err := provider.Token(req)
if err != nil {
return nil, errors.Wrap(errors.APIClientError, errors.AuthenticationFailure, err)
}
aReq := &auth.AuthenticatedRequest{
Timestamp: time.Now().Unix(),
RemoteAddress: ID,
Token: token,
Request: req,
}
jsonData, err := json.Marshal(aReq)
if err != nil {
return nil, errors.Wrap(errors.APIClientError, errors.JSONError, err)
}
response, err := srv.post(url, jsonData)
if err != nil {
return nil, err
}
result, ok := response.Result.(map[string]interface{})
if !ok {
return nil, errors.New(errors.APIClientError, errors.JSONError)
}
cert, ok := result["certificate"].(string)
if !ok {
return nil, errors.New(errors.APIClientError, errors.JSONError)
}
return []byte(cert), nil
}
// Sign sends a signature request to the remote CFSSL server,
// receiving a signed certificate or an error in response.
// It takes the serialized JSON request to send.
func (srv *Server) Sign(jsonData []byte) ([]byte, error) {
return srv.Req(jsonData, "sign")
}
// Info sends an info request to the remote CFSSL server, receiving a
// certificate or an error in response.
// It takes the serialized JSON request to send.
func (srv *Server) Info(jsonData []byte) ([]byte, error) {
return srv.Req(jsonData, "info")
}
// Req performs the common logic for Sign and Info, performing the actual
// request and returning the resultant certificate.
func (srv *Server) Req(jsonData []byte, target string) ([]byte, error) {
url := srv.getURL(target)
response, err := srv.post(url, jsonData)
if err != nil {
return nil, err
}
result := response.Result.(map[string]interface{})
cert := result["certificate"].(string)
if cert != "" {
return []byte(cert), nil
}
return nil, errors.Wrap(errors.APIClientError, errors.ClientHTTPError, stderr.New("response doesn't contain certificate."))
}

View File

@ -0,0 +1,59 @@
package client
import (
"net"
"testing"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/auth"
)
var (
testProvider auth.Provider
testKey = "0123456789ABCDEF0123456789ABCDEF"
testAD = []byte{1, 2, 3, 4} // IP address 1.2.3.4
)
func TestNewServer(t *testing.T) {
s := NewServer("1.1.1.1:::123456789")
if s != nil {
t.Fatalf("fatal error, server created with too many colons %v", s)
}
s2 := NewServer("1.1.1.1:[]")
if s != nil {
t.Fatalf("%v", s2)
}
_, port, _ := net.SplitHostPort("")
if port != "" {
t.Fatalf("%v", port)
}
}
func TestInvalidPort(t *testing.T) {
s := NewServer("1.1.1.1:99999999999999999999999999999")
if s != nil {
t.Fatalf("%v", s)
}
}
func TestAuthSign(t *testing.T) {
s := NewServer("1.1")
testProvider, _ = auth.New(testKey, nil)
testRequest := []byte(`testing 1 2 3`)
as, _ := s.AuthSign(testRequest, testAD, testProvider)
if as != nil {
t.Fatal("fatal error with auth sign function")
}
}
func TestSign(t *testing.T) {
s := NewServer("1.1")
sign, _ := s.Sign([]byte{5, 5, 5, 5})
if sign != nil {
t.Fatalf("%v", sign)
}
}

View File

@ -0,0 +1,300 @@
package generator
import (
"crypto/md5"
"crypto/sha1"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"io/ioutil"
"net/http"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/api"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/csr"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/log"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/universal"
)
// Sum contains digests for a certificate or certificate request.
type Sum struct {
MD5 string `json:"md5"`
SHA1 string `json:"sha-1"`
}
// Validator is a type of function that contains the logic for validating
// a certificate request.
type Validator func(*csr.CertificateRequest) error
// A CertRequest stores a PEM-encoded private key and corresponding
// CSR; this is returned from the CSR generation endpoint.
type CertRequest struct {
Key string `json:"private_key"`
CSR string `json:"certificate_request"`
Sums map[string]Sum `json:"sums"`
}
// A Handler accepts JSON-encoded certificate requests and
// returns a new private key and certificate request.
type Handler struct {
generator *csr.Generator
}
// NewHandler builds a new Handler from the
// validation function provided.
func NewHandler(validator Validator) (http.Handler, error) {
log.Info("setting up key / CSR generator")
return &api.HTTPHandler{
Handler: &Handler{
generator: &csr.Generator{Validator: validator},
},
Method: "POST",
}, nil
}
func computeSum(in []byte) (sum Sum, err error) {
var data []byte
p, _ := pem.Decode(in)
if p == nil {
err = errors.NewBadRequestString("not a CSR or certificate")
return
}
switch p.Type {
case "CERTIFICATE REQUEST":
var req *x509.CertificateRequest
req, err = x509.ParseCertificateRequest(p.Bytes)
if err != nil {
return
}
data = req.Raw
case "CERTIFICATE":
var cert *x509.Certificate
cert, err = x509.ParseCertificate(p.Bytes)
if err != nil {
return
}
data = cert.Raw
default:
err = errors.NewBadRequestString("not a CSR or certificate")
return
}
md5Sum := md5.Sum(data)
sha1Sum := sha1.Sum(data)
sum.MD5 = fmt.Sprintf("%X", md5Sum[:])
sum.SHA1 = fmt.Sprintf("%X", sha1Sum[:])
return
}
// Handle responds to requests for the CA to generate a new private
// key and certificate request on behalf of the client. The format for
// these requests is documented in the API documentation.
func (g *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
log.Info("request for CSR")
req := new(csr.CertificateRequest)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Warningf("failed to read request body: %v", err)
return errors.NewBadRequest(err)
}
err = json.Unmarshal(body, req)
if err != nil {
log.Warningf("failed to unmarshal request: %v", err)
return errors.NewBadRequest(err)
}
if req.CA != nil {
log.Warningf("request received with CA section")
return errors.NewBadRequestString("ca section only permitted in initca")
}
csr, key, err := g.generator.ProcessRequest(req)
if err != nil {
log.Warningf("failed to process CSR: %v", err)
// The validator returns a *cfssl/errors.HttpError
return err
}
sum, err := computeSum(csr)
if err != nil {
return errors.NewBadRequest(err)
}
// Both key and csr are returned PEM-encoded.
response := api.NewSuccessResponse(&CertRequest{
Key: string(key),
CSR: string(csr),
Sums: map[string]Sum{"certificate_request": sum},
})
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
err = enc.Encode(response)
return err
}
// A CertGeneratorHandler accepts JSON-encoded certificate requests
// and returns a new private key and signed certificate; it handles
// sending the CSR to the server.
type CertGeneratorHandler struct {
generator *csr.Generator
signer signer.Signer
}
// NewCertGeneratorHandler builds a new handler for generating
// certificates directly from certificate requests; the validator covers
// the certificate request and the CA's key and certificate are used to
// sign the generated request. If remote is not an empty string, the
// handler will send signature requests to the CFSSL instance contained
// in remote.
func NewCertGeneratorHandler(validator Validator, caFile, caKeyFile string, policy *config.Signing) (http.Handler, error) {
var err error
log.Info("setting up new generator / signer")
cg := new(CertGeneratorHandler)
if policy == nil {
policy = &config.Signing{
Default: config.DefaultConfig(),
Profiles: nil,
}
}
root := universal.Root{
Config: map[string]string{
"ca-file": caFile,
"ca-key-file": caKeyFile,
},
}
if cg.signer, err = universal.NewSigner(root, policy); err != nil {
log.Errorf("setting up signer failed: %v", err)
return nil, err
}
cg.generator = &csr.Generator{Validator: validator}
return api.HTTPHandler{Handler: cg, Method: "POST"}, nil
}
// NewCertGeneratorHandlerFromSigner returns a handler directly from
// the signer and validation function.
func NewCertGeneratorHandlerFromSigner(validator Validator, signer signer.Signer) http.Handler {
return api.HTTPHandler{
Handler: &CertGeneratorHandler{
generator: &csr.Generator{Validator: validator},
signer: signer,
},
Method: "POST",
}
}
type genSignRequest struct {
Hostname string `json:"hostname"`
Request *csr.CertificateRequest `json:"request"`
Profile string `json:"profile"`
Label string `json:"label"`
}
// Handle responds to requests for the CA to generate a new private
// key and certificate on behalf of the client. The format for these
// requests is documented in the API documentation.
func (cg *CertGeneratorHandler) Handle(w http.ResponseWriter, r *http.Request) error {
log.Info("request for CSR")
req := new(genSignRequest)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Warningf("failed to read request body: %v", err)
return errors.NewBadRequest(err)
}
err = json.Unmarshal(body, req)
if err != nil {
log.Warningf("failed to unmarshal request: %v", err)
return errors.NewBadRequest(err)
}
if req.Request == nil {
log.Warning("empty request received")
return errors.NewBadRequestString("missing request section")
}
if req.Request.CA != nil {
log.Warningf("request received with CA section")
return errors.NewBadRequestString("ca section only permitted in initca")
}
csr, key, err := cg.generator.ProcessRequest(req.Request)
if err != nil {
log.Warningf("failed to process CSR: %v", err)
// The validator returns a *cfssl/errors.HttpError
return err
}
var certPEM []byte
var profile *config.SigningProfile
policy := cg.signer.Policy()
if policy != nil && policy.Profiles != nil && req.Profile != "" {
profile = policy.Profiles[req.Profile]
}
if profile == nil && policy != nil {
profile = policy.Default
}
// This API does not override the subject because it was already added to the CSR
signReq := signer.SignRequest{
Hosts: signer.SplitHosts(req.Hostname),
Request: string(csr),
Profile: req.Profile,
Label: req.Label,
}
certBytes, err := cg.signer.Sign(signReq)
if err != nil {
log.Warningf("failed to sign request: %v", err)
return err
}
reqSum, err := computeSum(csr)
if err != nil {
return errors.NewBadRequest(err)
}
certSum, err := computeSum(certBytes)
if err != nil {
return errors.NewBadRequest(err)
}
result := map[string]interface{}{
"private_key": string(key),
"certificate_request": string(csr),
"certificate": string(certPEM),
"sums": map[string]Sum{
"certificate_request": reqSum,
"certificate": certSum,
},
}
return api.SendResponse(w, result)
}
// CSRValidate contains the default validation logic for certificate requests to
// the API server. This follows the Baseline Requirements for the Issuance and
// Management of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser
// Forum (https://cabforum.org). Specifically, section 10.2.3 ("Information
// Requirements"), states:
//
// "Applicant information MUST include, but not be limited to, at least one
// Fully-Qualified Domain Name or IP address to be included in the Certificates
// SubjectAltName extension."
func CSRValidate(req *csr.CertificateRequest) error {
if len(req.Hosts) == 0 {
log.Warning("request for CSR is missing the host parameter")
return errors.NewBadRequestMissingParameter("hosts")
}
return nil
}

View File

@ -0,0 +1,70 @@
package generator
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/csr"
)
func csrData(t *testing.T) *bytes.Reader {
req := &csr.CertificateRequest{
Names: []csr.Name{
{
C: "US",
ST: "California",
L: "San Francisco",
O: "CloudFlare",
OU: "Systems Engineering",
},
},
CN: "cloudflare.com",
Hosts: []string{"cloudflare.com"},
KeyRequest: &csr.KeyRequest{
Algo: "ecdsa",
Size: 256,
},
}
csrBytes, err := json.Marshal(req)
if err != nil {
t.Fatal(err)
}
return bytes.NewReader(csrBytes)
}
func TestGeneratorRESTfulVerbs(t *testing.T) {
handler, _ := NewHandler(CSRValidate)
ts := httptest.NewServer(handler)
data := csrData(t)
// POST should work.
req, _ := http.NewRequest("POST", ts.URL, data)
resp, _ := http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusOK {
t.Fatal(resp.Status)
}
// Test GET, PUT, DELETE and whatever, expect 400 errors.
req, _ = http.NewRequest("GET", ts.URL, data)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("PUT", ts.URL, data)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("DELETE", ts.URL, data)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("WHATEVER", ts.URL, data)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
}

View File

@ -0,0 +1,62 @@
package info
import (
"encoding/json"
"encoding/pem"
"io/ioutil"
"net/http"
"github.com/cloudflare/cfssl/bundler"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/api"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/api/client"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/log"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer"
)
// Handler is a type that contains the root certificates for the CA,
// and serves information on them for clients that need the certificates.
type Handler struct {
sign signer.Signer
}
// NewHandler creates a new handler to serve information on the CA's
// certificates, taking a signer to use.
func NewHandler(s signer.Signer) (http.Handler, error) {
return &api.HTTPHandler{
Handler: &Handler{
sign: s,
},
Method: "POST",
}, nil
}
// Handle listens for incoming requests for CA information, and returns
// a list containing information on each root certificate.
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
req := new(client.InfoReq)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Warningf("failed to read request body: %v", err)
return errors.NewBadRequest(err)
}
err = json.Unmarshal(body, req)
if err != nil {
log.Warningf("failed to unmarshal request: %v", err)
return errors.NewBadRequest(err)
}
cert, err := h.sign.Certificate(req.Label, req.Profile)
if err != nil {
return err
}
resp := client.InfoResp{
Certificate: bundler.PemBlockToString(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}),
}
response := api.NewSuccessResponse(resp)
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
return enc.Encode(response)
}

View File

@ -0,0 +1,127 @@
package info
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/api"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/local"
)
const (
testCaFile = "../testdata/ca.pem"
testCaKeyFile = "../testdata/ca_key.pem"
)
func newTestHandler(t *testing.T) (h http.Handler) {
signer, err := local.NewSignerFromFile(testCaFile, testCaKeyFile, nil)
if err != nil {
t.Fatal(err)
}
h, err = NewHandler(signer)
if err != nil {
t.Fatal(err)
}
return
}
func TestNewHandler(t *testing.T) {
newTestHandler(t)
}
func newInfoServer(t *testing.T) *httptest.Server {
ts := httptest.NewServer(newTestHandler(t))
return ts
}
func testInfoFile(t *testing.T, req map[string]interface{}) (resp *http.Response, body []byte) {
ts := newInfoServer(t)
defer ts.Close()
blob, err := json.Marshal(req)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
type infoTest struct {
RequestObject map[string]interface{}
ExpectedHTTPStatus int
ExpectedSuccess bool
ExpectedErrorCode int
}
var infoTests = []infoTest{
{
map[string]interface{}{
"label": "",
"profile": "",
},
http.StatusOK,
true,
0,
},
{
map[string]interface{}{
"label": "badlabel",
"profile": "",
},
http.StatusBadRequest,
false,
http.StatusBadRequest,
},
{
map[string]interface{}{
"label": 123,
},
http.StatusBadRequest,
false,
http.StatusBadRequest,
},
}
func TestInfo(t *testing.T) {
for i, test := range infoTests {
resp, body := testInfoFile(t, test.RequestObject)
if resp.StatusCode != test.ExpectedHTTPStatus {
t.Fatalf("Test %d: expected: %d, have %d", i, test.ExpectedHTTPStatus, resp.StatusCode)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}
message := new(api.Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess != message.Success {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedSuccess, message.Success)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess == true {
continue
}
if test.ExpectedErrorCode != message.Errors[0].Code {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedErrorCode, message.Errors[0].Code)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
}
}

View File

@ -0,0 +1,58 @@
package initca
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/cloudflare/cfssl/initca"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/api"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/csr"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/log"
)
// A NewCA contains a private key and certificate suitable for serving
// as the root key for a new certificate authority.
type NewCA struct {
Key string `json:"private_key"`
Cert string `json:"certificate"`
}
// initialCAHandler is an HTTP handler that accepts a JSON blob in the
// same format as the CSR endpoint; this blob should contain the
// identity information for the CA's root key. This endpoint is not
// suitable for creating intermediate certificates.
func initialCAHandler(w http.ResponseWriter, r *http.Request) error {
log.Info("setting up initial CA handler")
req := new(csr.CertificateRequest)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Warningf("failed to read request body: %v", err)
return errors.NewBadRequest(err)
}
err = json.Unmarshal(body, req)
if err != nil {
log.Warningf("failed to unmarshal request: %v", err)
return errors.NewBadRequest(err)
}
key, cert, err := initca.New(req)
if err != nil {
log.Warningf("failed to initialise new CA: %v", err)
return err
}
response := api.NewSuccessResponse(&NewCA{string(key), string(cert)})
enc := json.NewEncoder(w)
err = enc.Encode(response)
return err
}
// NewHandler returns a new http.Handler that handles request to
// initialize a CA.
func NewHandler() http.Handler {
return api.HTTPHandler{Handler: api.HandlerFunc(initialCAHandler), Method: "POST"}
}

View File

@ -0,0 +1,69 @@
package initca
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/csr"
)
func csrData(t *testing.T) *bytes.Reader {
req := &csr.CertificateRequest{
Names: []csr.Name{
{
C: "US",
ST: "California",
L: "San Francisco",
O: "CloudFlare",
OU: "Systems Engineering",
},
},
CN: "cloudflare.com",
Hosts: []string{"cloudflare.com"},
KeyRequest: &csr.KeyRequest{
Algo: "ecdsa",
Size: 256,
},
}
csrBytes, err := json.Marshal(req)
if err != nil {
t.Fatal(err)
}
return bytes.NewReader(csrBytes)
}
func TestInitCARESTfulVerbs(t *testing.T) {
ts := httptest.NewServer(NewHandler())
data := csrData(t)
// POST should work.
req, _ := http.NewRequest("POST", ts.URL, data)
resp, _ := http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusOK {
t.Fatal(resp.Status)
}
// Test GET, PUT, DELETE and whatever, expect 400 errors.
req, _ = http.NewRequest("GET", ts.URL, data)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("PUT", ts.URL, data)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("DELETE", ts.URL, data)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("WHATEVER", ts.URL, data)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
}

View File

@ -0,0 +1,314 @@
package sign
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/api"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/auth"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/log"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/universal"
)
// A Handler accepts requests with a hostname and certficate
// parameter (which should be PEM-encoded) and returns a new signed
// certificate. It includes upstream servers indexed by their
// profile name.
type Handler struct {
signer signer.Signer
}
// NewHandler generates a new Handler using the certificate
// authority private key and certficate to sign certificates. If remote
// is not an empty string, the handler will send signature requests to
// the CFSSL instance contained in remote by default.
func NewHandler(caFile, caKeyFile string, policy *config.Signing) (http.Handler, error) {
root := universal.Root{
Config: map[string]string{
"cert-file": caFile,
"key-file": caKeyFile,
},
}
s, err := universal.NewSigner(root, policy)
if err != nil {
log.Errorf("setting up signer failed: %v", err)
return nil, err
}
return NewHandlerFromSigner(s)
}
// NewHandlerFromSigner generates a new Handler directly from
// an existing signer.
func NewHandlerFromSigner(signer signer.Signer) (h *api.HTTPHandler, err error) {
policy := signer.Policy()
if policy == nil {
err = errors.New(errors.PolicyError, errors.InvalidPolicy)
return
}
// Sign will only respond for profiles that have no auth provider.
// So if all of the profiles require authentication, we return an error.
haveUnauth := (policy.Default.Provider == nil)
for _, profile := range policy.Profiles {
if haveUnauth {
break
}
haveUnauth = (profile.Provider == nil)
}
if !haveUnauth {
err = errors.New(errors.PolicyError, errors.InvalidPolicy)
return
}
return &api.HTTPHandler{
Handler: &Handler{
signer: signer,
},
Method: "POST",
}, nil
}
// This type is meant to be unmarshalled from JSON so that there can be a
// hostname field in the API
// TODO: Change the API such that the normal struct can be used.
type jsonSignRequest struct {
Hostname string `json:"hostname"`
Hosts []string `json:"hosts"`
Request string `json:"certificate_request"`
Subject *signer.Subject `json:"subject,omitempty"`
Profile string `json:"profile"`
Label string `json:"label"`
}
func jsonReqToTrue(js jsonSignRequest) signer.SignRequest {
sub := new(signer.Subject)
if js.Subject == nil {
sub = nil
} else {
// make a copy
*sub = *js.Subject
}
if js.Hostname != "" {
return signer.SignRequest{
Hosts: signer.SplitHosts(js.Hostname),
Subject: sub,
Request: js.Request,
Profile: js.Profile,
Label: js.Label,
}
}
return signer.SignRequest{
Hosts: js.Hosts,
Subject: sub,
Request: js.Request,
Profile: js.Profile,
Label: js.Label,
}
}
// Handle responds to requests for the CA to sign the certificate request
// present in the "certificate_request" parameter for the host named
// in the "hostname" parameter. The certificate should be PEM-encoded. If
// provided, subject information from the "subject" parameter will be used
// in place of the subject information from the CSR.
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
log.Info("signature request received")
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
r.Body.Close()
var req jsonSignRequest
err = json.Unmarshal(body, &req)
if err != nil {
return errors.NewBadRequestString("Unable to parse sign request")
}
signReq := jsonReqToTrue(req)
if len(signReq.Hosts) == 0 {
return errors.NewBadRequestString("missing parameter 'hostname' or 'hosts'")
}
if req.Request == "" {
return errors.NewBadRequestString("missing parameter 'certificate_request'")
}
var cert []byte
var profile *config.SigningProfile
policy := h.signer.Policy()
if policy != nil && policy.Profiles != nil && req.Profile != "" {
profile = policy.Profiles[req.Profile]
}
if profile == nil && policy != nil {
profile = policy.Default
}
if profile.Provider != nil {
log.Error("profile requires authentication")
return errors.NewBadRequestString("authentication required")
}
cert, err = h.signer.Sign(signReq)
if err != nil {
log.Warningf("failed to sign request: %v", err)
return err
}
result := map[string]string{"certificate": string(cert)}
log.Info("wrote response")
return api.SendResponse(w, result)
}
// An AuthHandler verifies and signs incoming signature requests.
type AuthHandler struct {
signer signer.Signer
}
// NewAuthHandler generates a new AuthHandler using the certificate
// authority private key and certficate to sign certificates. If remote
// is not an empty string, the handler will send signature requests to
// the CFSSL instance contained in remote by default.
func NewAuthHandler(caFile, caKeyFile string, policy *config.Signing) (http.Handler, error) {
root := universal.Root{
Config: map[string]string{
"cert-file": caFile,
"key-file": caKeyFile,
},
}
s, err := universal.NewSigner(root, policy)
if err != nil {
log.Errorf("setting up signer failed: %v", err)
return nil, err
}
return NewAuthHandlerFromSigner(s)
}
// NewAuthHandlerFromSigner creates a new AuthHandler from the signer
// that is passed in.
func NewAuthHandlerFromSigner(signer signer.Signer) (http.Handler, error) {
policy := signer.Policy()
if policy == nil {
return nil, errors.New(errors.PolicyError, errors.InvalidPolicy)
}
if policy.Default == nil && policy.Profiles == nil {
return nil, errors.New(errors.PolicyError, errors.InvalidPolicy)
}
// AuthSign will not respond for profiles that have no auth provider.
// So if there are no profiles with auth providers in this policy,
// we return an error.
haveAuth := (policy.Default.Provider != nil)
for _, profile := range policy.Profiles {
if haveAuth {
break
}
haveAuth = (profile.Provider != nil)
}
if !haveAuth {
return nil, errors.New(errors.PolicyError, errors.InvalidPolicy)
}
return &api.HTTPHandler{
Handler: &AuthHandler{
signer: signer,
},
Method: "POST",
}, nil
}
// Handle receives the incoming request, validates it, and processes it.
func (h *AuthHandler) Handle(w http.ResponseWriter, r *http.Request) error {
log.Info("signature request received")
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Errorf("failed to read response body: %v", err)
return err
}
r.Body.Close()
var aReq auth.AuthenticatedRequest
err = json.Unmarshal(body, &aReq)
if err != nil {
log.Errorf("failed to unmarshal authenticated request: %v", err)
return errors.NewBadRequest(err)
}
var req jsonSignRequest
err = json.Unmarshal(aReq.Request, &req)
if err != nil {
log.Errorf("failed to unmarshal request from authenticated request: %v", err)
return errors.NewBadRequestString("Unable to parse authenticated sign request")
}
signReq := jsonReqToTrue(req)
if len(signReq.Hosts) == 0 {
return errors.NewBadRequestString("missing parameter 'hostname' or 'hosts'")
}
if req.Request == "" {
return errors.NewBadRequestString("missing parameter 'certificate_request'")
}
// Sanity checks to ensure that we have a valid policy. This
// should have been checked in NewAuthHandler.
policy := h.signer.Policy()
if policy == nil {
log.Critical("signer was initialised without a signing policy")
return errors.NewBadRequestString("invalid policy")
}
profile := policy.Default
if policy.Profiles != nil {
profile = policy.Profiles[req.Profile]
}
if profile == nil {
log.Critical("signer was initialised without any valid profiles")
return errors.NewBadRequestString("invalid profile")
}
if profile.Provider == nil {
log.Error("profile has no authentication provider")
return errors.NewBadRequestString("no authentication provider")
}
if !profile.Provider.Verify(&aReq) {
log.Warning("received authenticated request with invalid token")
return errors.NewBadRequestString("invalid token")
}
if req.Hostname == "" && len(req.Hosts) == 0 {
return errors.NewBadRequestString("missing hostnames")
}
if req.Request == "" {
return errors.NewBadRequestString("missing certificate_request parameter")
}
cert, err := h.signer.Sign(jsonReqToTrue(req))
if err != nil {
log.Errorf("signature failed: %v", err)
return err
}
result := map[string]string{"certificate": string(cert)}
log.Info("wrote response")
return api.SendResponse(w, result)
}

View File

@ -0,0 +1,400 @@
package sign
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/api"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/auth"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
)
const (
testCaFile = "../testdata/ca.pem"
testCaKeyFile = "../testdata/ca_key.pem"
testCSRFile = "../testdata/csr.pem"
testBrokenCertFile = "../testdata/broken.pem"
testBrokenCSRFile = "../testdata/broken_csr.pem"
)
var validLocalConfig = `
{
"signing": {
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "1m"
}
}
}`
var validAuthLocalConfig = `
{
"signing": {
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "1m",
"auth_key": "sample"
}
},
"auth_keys": {
"sample": {
"type":"standard",
"key":"0123456789ABCDEF0123456789ABCDEF"
}
}
}`
func newTestHandler(t *testing.T) (h http.Handler) {
h, err := NewHandler(testCaFile, testCaKeyFile, nil)
if err != nil {
t.Fatal(err)
}
return
}
func TestNewHandler(t *testing.T) {
newTestHandler(t)
}
func TestNewHandlerWithProfile(t *testing.T) {
conf, err := config.LoadConfig([]byte(validLocalConfig))
if err != nil {
t.Fatal(err)
}
_, err = NewHandler(testCaFile, testCaKeyFile, conf.Signing)
if err != nil {
t.Fatal(err)
}
}
func TestNewHandlerWithAuthProfile(t *testing.T) {
conf, err := config.LoadConfig([]byte(validAuthLocalConfig))
if err != nil {
t.Fatal(err)
}
_, err = NewHandler(testCaFile, testCaKeyFile, conf.Signing)
if err == nil {
t.Fatal("All profiles have auth keys. Should have failed to create non-auth sign handler.")
}
}
func TestNewHandlerError(t *testing.T) {
// using testBrokenCSRFile as badly formed key
_, err := NewHandler(testCaFile, testBrokenCSRFile, nil)
if err == nil {
t.Fatal("Expect error when create a signer with broken file.")
}
}
func newSignServer(t *testing.T) *httptest.Server {
ts := httptest.NewServer(newTestHandler(t))
return ts
}
func testSignFileOldInterface(t *testing.T, hostname, csrFile string) (resp *http.Response, body []byte) {
ts := newSignServer(t)
defer ts.Close()
var csrPEM []byte
if csrFile != "" {
var err error
csrPEM, err = ioutil.ReadFile(csrFile)
if err != nil {
t.Fatal(err)
}
}
obj := map[string]string{}
if len(hostname) > 0 {
obj["hostname"] = hostname
}
if len(csrPEM) > 0 {
obj["certificate_request"] = string(csrPEM)
}
blob, err := json.Marshal(obj)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
func testSignFile(t *testing.T, hostname, csrFile string) (resp *http.Response, body []byte) {
ts := newSignServer(t)
defer ts.Close()
var csrPEM []byte
if csrFile != "" {
var err error
csrPEM, err = ioutil.ReadFile(csrFile)
if err != nil {
t.Fatal(err)
}
}
obj := map[string]interface{}{}
if len(hostname) > 0 {
obj["hosts"] = []string{hostname}
}
if len(csrPEM) > 0 {
obj["certificate_request"] = string(csrPEM)
}
blob, err := json.Marshal(obj)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
const (
testHostName = "localhost"
testDomainName = "cloudflare.com"
)
type signTest struct {
Hostname string
CSRFile string
ExpectedHTTPStatus int
ExpectedSuccess bool
ExpectedErrorCode int
}
var signTests = []signTest{
{
testHostName,
testCSRFile,
http.StatusOK,
true,
0,
},
{
testDomainName,
testCSRFile,
http.StatusOK,
true,
0,
},
{
"",
testCSRFile,
http.StatusBadRequest,
false,
http.StatusBadRequest,
},
{
testDomainName,
"",
http.StatusBadRequest,
false,
http.StatusBadRequest,
},
{
testDomainName,
testBrokenCSRFile,
http.StatusBadRequest,
false,
1002,
},
}
func TestSign(t *testing.T) {
for i, test := range signTests {
resp, body := testSignFile(t, test.Hostname, test.CSRFile)
if resp.StatusCode != test.ExpectedHTTPStatus {
t.Logf("Test %d: expected: %d, have %d", i, test.ExpectedHTTPStatus, resp.StatusCode)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}
message := new(api.Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Logf("failed to read response body: %v", err)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess != message.Success {
t.Logf("Test %d: expected: %v, have %v", i, test.ExpectedSuccess, message.Success)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess == true {
continue
}
if test.ExpectedErrorCode != message.Errors[0].Code {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedErrorCode, message.Errors[0].Code)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
}
// Test for backward compatibility
// TODO remove after API transition is complete.
for i, test := range signTests {
resp, body := testSignFileOldInterface(t, test.Hostname, test.CSRFile)
if resp.StatusCode != test.ExpectedHTTPStatus {
t.Logf("Test %d: expected: %d, have %d", i, test.ExpectedHTTPStatus, resp.StatusCode)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}
message := new(api.Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Logf("failed to read response body: %v", err)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess != message.Success {
t.Logf("Test %d: expected: %v, have %v", i, test.ExpectedSuccess, message.Success)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess == true {
continue
}
if test.ExpectedErrorCode != message.Errors[0].Code {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedErrorCode, message.Errors[0].Code)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
}
}
func newTestAuthHandler(t *testing.T) http.Handler {
conf, err := config.LoadConfig([]byte(validAuthLocalConfig))
if err != nil {
t.Fatal(err)
}
h, err := NewAuthHandler(testCaFile, testCaKeyFile, conf.Signing)
if err != nil {
t.Fatal(err)
}
return h
}
func TestNewAuthHandler(t *testing.T) {
newTestAuthHandler(t)
}
func TestNewAuthHandlerWithNoAuthConfig(t *testing.T) {
conf, err := config.LoadConfig([]byte(validLocalConfig))
if err != nil {
t.Fatal(err)
}
_, err = NewAuthHandler(testCaFile, testCaKeyFile, conf.Signing)
if err == nil {
t.Fatal("Config doesn't have auth keys. Should have failed.")
}
return
}
func testAuthSignFile(t *testing.T, hostname, csrFile string, profile *config.SigningProfile) (resp *http.Response, body []byte) {
ts := newAuthSignServer(t)
defer ts.Close()
var csrPEM []byte
if csrFile != "" {
var err error
csrPEM, err = ioutil.ReadFile(csrFile)
if err != nil {
t.Fatal(err)
}
}
obj := map[string]string{}
if len(hostname) > 0 {
obj["hostname"] = hostname
}
if len(csrPEM) > 0 {
obj["certificate_request"] = string(csrPEM)
}
reqBlob, err := json.Marshal(obj)
if err != nil {
t.Fatal(err)
}
var aReq auth.AuthenticatedRequest
aReq.Request = reqBlob
aReq.Token, err = profile.Provider.Token(aReq.Request)
if err != nil {
t.Fatal(err)
}
blob, err := json.Marshal(aReq)
if err != nil {
t.Fatal(err)
}
resp, err = http.Post(ts.URL, "application/json", bytes.NewReader(blob))
if err != nil {
t.Fatal(err)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
return
}
func newAuthSignServer(t *testing.T) *httptest.Server {
ts := httptest.NewServer(newTestAuthHandler(t))
return ts
}
func TestAuthSign(t *testing.T) {
conf, err := config.LoadConfig([]byte(validAuthLocalConfig))
if err != nil {
t.Fatal(err)
}
for i, test := range signTests {
resp, body := testAuthSignFile(t, test.Hostname, test.CSRFile, conf.Signing.Default)
if resp.StatusCode != test.ExpectedHTTPStatus {
t.Logf("Test %d: expected: %d, have %d", i, test.ExpectedHTTPStatus, resp.StatusCode)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}
message := new(api.Response)
err := json.Unmarshal(body, message)
if err != nil {
t.Logf("failed to read response body: %v", err)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess != message.Success {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedSuccess, message.Success)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
if test.ExpectedSuccess == true {
continue
}
if test.ExpectedErrorCode != message.Errors[0].Code {
t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedErrorCode, message.Errors[0].Code)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, message)
}
}
}

View File

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIICATCCAWoCCQDidF+uNJR6czANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB
cyBQdHkgTHRkMB4XDTEyMDUwMTIyNTUxN1oXDTEzMDUwMTIyNTUxN1owRTELMAkG
A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
nodhz31kLEJoeLSkRmrv8l7exkGtO0REtIbirj9BBy64ZXVBE7khKGO2cnM8U7yj
w7Ntfh+IvCjZVA3d2XqHS3Pjrt4HmU/cGCONE8+NEXoqdzLUDPOix1qDDRBvXs81
IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtpjl
KAV2qh6CYHZbdqixhDerjvJcD4Nsd7kExEZfHuECAwEAATANBgkqhkiG9w0BAQUF
AAOBgQCyOqs7+qpMrYCgL6OamDeCVojLoEp036PsnaYWf2NPmsVXdpYW40Foyyjp
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
iv5otkxO5rxtGPv7o2J1eMBpCuSkydvoz3Ey/QwGqbBwEXQ4xYCgra336gqW2KQt
+LnDCkE8f5oBhCIisExc2i8PDvsRsY70g/2gs983ImJjVR8sDw==
-----END CERTIFICATE-----

View File

@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIFGzCCAwUCAQAwgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMR0wGwYDVQQDExRjbG91ZGZsYXJl
LWludGVyLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOUKdX6+
PSxU/LxKocsCUj7HCc+FaDOPZV68Po3PVm7UF5DmbnLgJYJ/4aZEZM/v5r8LnXQX
DqumYicHQ2DHHBDasLTx8m0KeKOUYf9WMQ8gdjmVFoCiZwzxGDHok66/0Glkkqmv
2nJQxXncl5ZFta4sfmcQx3KT02l61LaBbG3j8PbRCWEr+0eRE6twuYRR13AgZ3AT
wnMjzxzvsW67qmAy0cq+XgYYfTK9vhPs+8J0fxXa0Iftu3yuhd30xLIVXLu45GR+
i6KnsSxVERSaVxjkS+lHXjUpdtmqI5CK6wn67vqYRRA2TzAJHX8Jb+KL2/UEo5WN
fAJ8S0heODQA8nHVU1JIfpegOlQRMv55DgnQUv1c1uwO5hqvv7MPQ3X/m9Kjccs1
FBH1/SVuzKyxYEQ34LErX3HI+6avbVnRtTR/UHkfnZVIXSrcjUm73BGj33hrtiKl
0ZyZnaUKGZPuvebOUFNiXemhTbqrfi/zAb1Tsm/h+xkn5EZ5sMj5NHdAbpih3TqX
2gRhnFZcFjtJM6zzC5O7eG5Kdqf8iladXTXtWxzrUPkb5CupzFl1dyS3dqdkoIXv
kmlScnu+6jBOaYeVvwogxr2Y69y4Zfg/qbPyBOLZquX9ovbuSP1DQmC//LV5t7YH
HY/1MXr5U0MMvcn+9JWUV6ou3at4AgEqfK0vAgMBAAGgSzBJBgkqhkiG9w0BCQ4x
PDA6MDgGA1UdEQQxMC+CFGNsb3VkZmxhcmUtaW50ZXIuY29tghd3d3djbG91ZGZs
YXJlLWludGVyLmNvbTALBgkqhkiG9w0BAQ0DggIBAHtSt/v+IHQmSK5UiQWwjRWA
ZezIWVlJuselW8DEPNHzDtnraVhjPSFP995Cqh9fc89kx2Bt9hDhjNteTB+pJW6B
aCRRZygJ6/m3Ii1XqTFgfEJBWwuIX1Req0PCW/ayegdLzzYbSZ31wRICCveBQyGw
vRtzIBUeMvz9MgLJ8zx7eN7fDhrvy+Y1SkC4g0sAQTYYfM9P/He4k5hx79hmd2YC
mUDAlNZV0g0dY0qR4cITmhniIFW5iZBplY7DmqooUXrj5yEga2QMj/RA16lPzHbz
7ceUlcH2L6/V6zMR/rfCiGRoWInxWSuuJhLIVLmoEo0590w6KVEZifHxsRpl4l09
imvzwTSQGIrY8jF9AxOD0rRA9wXCT9h8XtBWyJZ1/DmzJG8+7oZ/HdE9XhzwNujD
Q6lBOj+dznju7k/snYCZVq501JLPeql8vQrq0O/xSqSK4yN1IG4NisZeDK2BZEOy
QhnKXodIKf+zXnFw86lZ/ZwHQFr6jOSxmbrZ2OiY34m7Yd9oeIaMPviysRih2x4Q
O6DFz72f97+xFZuXIbmn8DPQV8U9bk/gbrfUCPnx/icS8UoPsBKc9Gio0FZO4+8A
4/ac3oeN0zy/WjsBP+J50CRUXMrRI9KO+/bI4pcT14B31YbuSo6ygIkIkj7YDh36
+4ZG6HnUPQI8HteF9hzp=BROKEN==
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICyDCCAjGgAwIBAgIJAPCgd7rafQZGMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp
c2NvMRMwEQYDVQQKDApDbG91ZEZsYXJlMRQwEgYDVQQLDAtERVZfVEVTVElORzEW
MBQGA1UEAwwNQ0ZTU0xfVEVTVF9DQTAeFw0xNDA0MTExNjQyMjBaFw0yNDA0MDgx
NjQyMjBaMH0xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYD
VQQHDA1TYW4gRnJhbmNpc2NvMRMwEQYDVQQKDApDbG91ZEZsYXJlMRQwEgYDVQQL
DAtERVZfVEVTVElORzEWMBQGA1UEAwwNQ0ZTU0xfVEVTVF9DQTCBnzANBgkqhkiG
9w0BAQEFAAOBjQAwgYkCgYEAm6f+jkP2t5q/vM0YAUZZkhq/EAYD+L1CMS59jJOL
omfDnKUWOGKi/k7URBg1HNL3vm7/ESDazZWFy9l/nibWxNkSUPkQIrvrGsNivkRU
zXkwgNX8IN8LOYAQ3BWxAqitXTpLjf4FeCTB6G59v9eYlAX3kicXRdY+cqhEvLFb
u3MCAwEAAaNQME4wHQYDVR0OBBYEFLhe765nULfW8wflar5Vs2c6DZI+MB8GA1Ud
IwQYMBaAFLhe765nULfW8wflar5Vs2c6DZI+MAwGA1UdEwQFMAMBAf8wDQYJKoZI
hvcNAQEFBQADgYEABYqqOUq3ZrtMYaTAoeA7Cr/OBMjBV+/TiOe8fRNoPZ7+aKSg
E1baohCGqougm+/XOtBXeLv5tVQihz/2iKdwHmX4HjkxzevAXyazjxeW4IDA21Jl
fKd7xUJHM0Du/opoDkXWr/vRVztOB33ndlAK7ruSLfTR3E9HoUe3aRH7ceQ=
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEmzCCA4OgAwIBAgIMAMSvNBgypwaaSQ5iMA0GCSqGSIb3DQEBBQUAMIGMMQsw
CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy
YW5jaXNjbzETMBEGA1UEChMKQ0ZTU0wgVEVTVDEbMBkGA1UEAxMSQ0ZTU0wgVEVT
VCBSb290IENBMR4wHAYJKoZIhvcNAQkBFg90ZXN0QHRlc3QubG9jYWwwHhcNMTIx
MjEyMDIxMDMxWhcNMjIxMDIxMDIxMDMxWjCBjDELMAkGA1UEBhMCVVMxEzARBgNV
BAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzARBgNVBAoT
CkNGU1NMIFRFU1QxGzAZBgNVBAMTEkNGU1NMIFRFU1QgUm9vdCBDQTEeMBwGCSqG
SIb3DQEJARYPdGVzdEB0ZXN0LmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAsRp1xSfIDoD/40Bo4Hls3sFn4dav5NgxbZGpVyGF7dJI9u0eEnL4
BUGssPaUFLWC83CZxujUEiEfE0oKX+uOhhGv3+j5xSTNM764m2eSiN53cdZtK05d
hwq9uS8LtjKOQeN1mQ5qmiqxBMdjkKgMsVw5lMCgoYKo57kaKFyXzdpNVDzqw+pt
HWmuNtDQjK3qT5Ma06mYPmIGYhIZYLY7oJGg9ZEaNR0GIw4zIT5JRsNiaSb5wTLw
aa0n/4vLJyVjLJcYmJBvZWj8g+taK+C4INu/jGux+bmsC9hq14tbOaTNAn/NE0qN
8oHwcRBEqfOdEYdZkxI5NWPiKNW/Q+AeXQIDAQABo4H6MIH3MB0GA1UdDgQWBBS3
0veEuqg51fusEM4p/YuWpBPsvTCBxAYDVR0jBIG8MIG5gBS30veEuqg51fusEM4p
/YuWpBPsvaGBkqSBjzCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3Ju
aWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzARBgNVBAoTCkNGU1NMIFRFU1Qx
GzAZBgNVBAMTEkNGU1NMIFRFU1QgUm9vdCBDQTEeMBwGCSqGSIb3DQEJARYPdGVz
dEB0ZXN0LmxvY2FsggwAxK80GDKnBppJDmIwDwYDVR0TBAgwBgEB/wIBADANBgkq
hkiG9w0BAQUFAAOCAQEAJ7r1EZYDwed6rS0+YKHdkRGRQ5Rz6A9DIVBPXrSMAGj3
F5EF2m/GJbhpVbnNJTVlgP9DDyabOZNxzdrCr4cHMkYYnocDdgAodnkw6GZ/GJTc
depbVTR4TpihFNzeDEGJePrEwM1DouGswpu97jyuCYZ3z1a60+a+3C1GwWaJ7Aet
Uqm+yLTUrMISsfnDPqJdM1NeqW3jiZ4IgcqJkieCCSpag9Xuzrp9q6rjmePvlQkv
qz020JGg6VijJ+c6Tf5y0XqbAhkBTqYtVamu9gEth9utn12EhdNjTZMPKMjjgFUd
H0N6yOEuQMl4ky7RxZBM0iPyeob6i4z2LEQilgv9MQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxGnXFJ8gOgP/j
QGjgeWzewWfh1q/k2DFtkalXIYXt0kj27R4ScvgFQayw9pQUtYLzcJnG6NQSIR8T
Sgpf646GEa/f6PnFJM0zvribZ5KI3ndx1m0rTl2HCr25Lwu2Mo5B43WZDmqaKrEE
x2OQqAyxXDmUwKChgqjnuRooXJfN2k1UPOrD6m0daa420NCMrepPkxrTqZg+YgZi
EhlgtjugkaD1kRo1HQYjDjMhPklGw2JpJvnBMvBprSf/i8snJWMslxiYkG9laPyD
61or4Lgg27+Ma7H5uawL2GrXi1s5pM0Cf80TSo3ygfBxEESp850Rh1mTEjk1Y+Io
1b9D4B5dAgMBAAECggEAKHhjcSomDSptTwDo9mLI/h40HudwSlsc8GzYxZBjinUD
N2n39T9QbeMUE1xFenX/9qFEgq+xxnLLJx1EQacSapCgIAqdCO/f9HMgvGJumdg8
c0cMq1i9Bp7tu+OESZ5D48qWlOM2eQRIb08g8W11eRIaFmPuUPoKnuktkQuXpPJc
YbS/+JuA8SDwe6sV0cMCQuS+iHFfeGwWCKrDUkhLwcL3waW3od2XFyOeFFWFhl0h
HmM/mWKRuRdqR7hrmArTwFZVkB+o/1ywVYXIv+JQm0eNZ5PKLNJGL2f5oxbMR/JI
AoK0bAlJmYaFp96h1KpbPwLEL/0hHSWA7sAyJIgQAQKBgQDaEAZor/w4ZUTekT1+
cbId0yA+ikDXQOfXaNCSh9Pex+Psjd5zVVOqyVFJ29daRju3d7rmpN4Cm5V4h0l1
/2ad207rjCAnpCHtaddJWNyJzF2IL2IaoCZQRp0k7zOjBGQpoWDTwBaEin5CCv3P
kkdQkKz6FDP1xskHSLZr21/QCQKBgQDP6jXutEgGjf3yKpMFk/69EamJdon8clbt
hl7cOyWtobnZhdOWVZPe00Oo3Jag2aWgFFsm3EtwnUCnR4d4+fXRKS2LkhfIUZcz
cKy17Ileggdd8UGhL4RDrF/En9tJL86WcVkcoOrqLcGB2FLWrVhVpHFK74eLMCH/
uc/+ioPItQKBgHYoDsD08s7AGMQcoNx90MyWVLduhFnegoFW+wUa8jOZzieka6/E
wVQeR5yksZjpy3vLNYu6M83n7eLkM2rrm/fXGHlLcTTpm7SgEBZfPwivotKjEh5p
PrlqucWEk082lutz1RqHz+u7e1Rfzk2F7nx6GDBdeBYpw03eGXJx6QW5AoGBAIJq
4puyAEAET1fZNtHX7IGCk7sDXTi6LCbgE57HhzHr8V0t4fQ6CABMuvMwM1gATjEk
s6yjoLqqGUUUzDipanViBAy5fiuManC868lN7zkWDTLzQ3ytBqVAee4na/DziP27
ae9YTSLJwskE/alloLRP6zTbHUXE0n7LelmrX1DFAoGBAMFLl+Lu+WFgCHxBjn43
rHpJbQZQmsFhAMhkN4hsj6dJfAGn2gRLRiVRAika+8QF65xMZiVQWUVSUZADWERi
0SXGjzN1wYxO3Qzy3LYwws6fxFAq5lo79eb38yFT2lHdqK3x/QgiDSRVl+R6cExV
xQB518/lp2eIeMpglWByDwJX
-----END PRIVATE KEY-----

View File

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB7jCCAVmgAwIBAgIBADALBgkqhkiG9w0BAQUwJjEQMA4GA1UEChMHQWNtZSBD
bzESMBAGA1UEAxMJMTI3LjAuMC4xMB4XDTEyMDkwNzIyMDAwNFoXDTEzMDkwNzIy
MDUwNFowJjEQMA4GA1UEChMHQWNtZSBDbzESMBAGA1UEAxMJMTI3LjAuMC4xMIGd
MAsGCSqGSIb3DQEBAQOBjQAwgYkCgYEAm6f+jkP2t5q/vM0YAUZZkhq/EAYD+L1C
MS59jJOLomfDnKUWOGKi/k7URBg1HNL3vm7/ESDazZWFy9l/nibWxNkSUPkQIrvr
GsNivkRUzXkwgNX8IN8LOYAQ3BWxAqitXTpLjf4FeCTB6G59v9eYlAX3kicXRdY+
cqhEvLFbu3MCAwEAAaMyMDAwDgYDVR0PAQH/BAQDAgCgMA0GA1UdDgQGBAQBAgME
MA8GA1UdIwQIMAaABAECAwQwCwYJKoZIhvcNAQEFA4GBABndWRIcfi+QB9Sakr+m
dYnXTgYCnFio53L2Z+6EHTGG+rEhWtUEGhL4p4pzXX4siAnjWvwcgXTo92cafcfi
uB7wRfK+NL9CTJdpN6cdL+fiNHzH8hsl3bj1nL0CSmdn2hkUWVLbLhSgWlib/I8O
aq+K7aVrgHkPnWeRiG6tl+ZA
-----END CERTIFICATE-----

View File

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIB0jCCAVcCAQAwgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMR0wGwYDVQQDExRjbG91ZGZsYXJl
LWludGVyLmNvbTB2MBAGByqGSM49AgEGBSuBBAAiA2IABCFZIzSRsH9xdF1iR+8k
ElbcbqAYnYuSTbEOxYcREHGRJd2/v9YhetEwWNmIuisCbgOpyBO9zyFxsnzYU4cO
A/AomW2nJEP7n4M9g8r8clhQz8y6+013jP9MEqf4pqMVnqBLMEkGCSqGSIb3DQEJ
DjE8MDowOAYDVR0RBDEwL4IUY2xvdWRmbGFyZS1pbnRlci5jb22CF3d3d2Nsb3Vk
ZmxhcmUtaW50ZXIuY29tMAoGCCqGSM49BAMDA2kAMGYCMQD6kSGGc3/DeFAWrPUX
qSlnTTm57DpzUoHQE306DfbFB6DFfoORNM5Z98chnZ+Ell4CMQCzYhOvIh3+GPGF
MuYYIAfQV2JG+n7pjfpJ+X1Ee2bOtA4ZO39P9/FTEtJUXt+Ivqw=
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,53 @@
-----BEGIN CERTIFICATE-----
MIIEizCCA/agAwIBAgIIeM7v534l+W0wCwYJKoZIhvcNAQELMH0xCzAJBgNVBAYT
AlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2Nv
MRMwEQYDVQQKDApDbG91ZEZsYXJlMRQwEgYDVQQLDAtERVZfVEVTVElORzEWMBQG
A1UEAwwNQ0ZTU0xfVEVTVF9DQTAeFw0xNDA0MTEyMTIyMzdaFw0xOTA0MTEyMTI3
MzdaMIGMMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQ2xvdWRGbGFyZTEcMBoGA1UE
CxMTU3lzdGVtcyBFbmdpbmVlcmluZzEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzET
MBEGA1UECBMKQ2FsaWZvcm5pYTEdMBsGA1UEAxMUY2xvdWRmbGFyZS1pbnRlci5j
b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDlCnV+vj0sVPy8SqHL
AlI+xwnPhWgzj2VevD6Nz1Zu1BeQ5m5y4CWCf+GmRGTP7+a/C510Fw6rpmInB0Ng
xxwQ2rC08fJtCnijlGH/VjEPIHY5lRaAomcM8Rgx6JOuv9BpZJKpr9pyUMV53JeW
RbWuLH5nEMdyk9NpetS2gWxt4/D20QlhK/tHkROrcLmEUddwIGdwE8JzI88c77Fu
u6pgMtHKvl4GGH0yvb4T7PvCdH8V2tCH7bt8roXd9MSyFVy7uORkfouip7EsVREU
mlcY5EvpR141KXbZqiOQiusJ+u76mEUQNk8wCR1/CW/ii9v1BKOVjXwCfEtIXjg0
APJx1VNSSH6XoDpUETL+eQ4J0FL9XNbsDuYar7+zD0N1/5vSo3HLNRQR9f0lbsys
sWBEN+CxK19xyPumr21Z0bU0f1B5H52VSF0q3I1Ju9wRo994a7YipdGcmZ2lChmT
7r3mzlBTYl3poU26q34v8wG9U7Jv4fsZJ+RGebDI+TR3QG6Yod06l9oEYZxWXBY7
STOs8wuTu3huSnan/IpWnV017Vsc61D5G+QrqcxZdXckt3anZKCF75JpUnJ7vuow
TmmHlb8KIMa9mOvcuGX4P6mz8gTi2arl/aL27kj9Q0Jgv/y1ebe2Bx2P9TF6+VND
DL3J/vSVlFeqLt2reAIBKnytLwIDAQABo4GDMIGAMA4GA1UdDwEB/wQEAwIApDAS
BgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBTXXUgpaSwO9HOrQBxGqOOSFHsH
EDAfBgNVHSMEGDAWgBS4Xu+uZ1C31vMH5Wq+VbNnOg2SPjAaBgNVHREEEzARgg9j
ZnNzbC1pbnRlci5jb20wCwYJKoZIhvcNAQELA4GBABqJOYgV+qEgkG/BIgsGaJ/Z
Neey0x0MwxPvA87e24GiYxYXX8ypR2DfLtuSjYfT0PVOWI5+3o9b3wnHhOu0aVe8
YK/7XUWOakt8Jv/fE0fGs4Ps5IeMynWBgwrf/6IQWEfnf/1siCrTf0yUEn0PMGu6
q2sLytoPYeibTYLuP1ED
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEbjCCAligAwIBAgIIeHSbZwALpoAwCwYJKoZIhvcNAQELMIGMMQswCQYDVQQG
EwJVUzETMBEGA1UEChMKQ2xvdWRGbGFyZTEcMBoGA1UECxMTU3lzdGVtcyBFbmdp
bmVlcmluZzEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZv
cm5pYTEdMBsGA1UEAxMUY2xvdWRmbGFyZS1pbnRlci5jb20wHhcNMTQwNDExMjEy
MjM4WhcNMTkwNDExMjEyNzM4WjCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkNs
b3VkRmxhcmUxHDAaBgNVBAsTE1N5c3RlbXMgRW5naW5lZXJpbmcxFjAUBgNVBAcT
DVNhbiBGcmFuY2lzY28xEzARBgNVBAgTCkNhbGlmb3JuaWExHTAbBgNVBAMTFGNs
b3VkZmxhcmUtaW50ZXIuY29tMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEIVkjNJGw
f3F0XWJH7yQSVtxuoBidi5JNsQ7FhxEQcZEl3b+/1iF60TBY2Yi6KwJuA6nIE73P
IXGyfNhThw4D8CiZbackQ/ufgz2DyvxyWFDPzLr7TXeM/0wSp/imoxWeo4GDMIGA
MA4GA1UdDwEB/wQEAwIApDASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBRB
+YoiUjIm34/wBwHdJGE4Wufs/DAfBgNVHSMEGDAWgBTXXUgpaSwO9HOrQBxGqOOS
FHsHEDAaBgNVHREEEzARgg9jZnNzbC1pbnRlci5jb20wCwYJKoZIhvcNAQELA4IC
AQCaj2i8wr9r3FS8Tw5QHD+tPmryrHsiLlERVanTif9kt/fRc1/hm/pv2lTLK8kK
U5Eti1jCB2T/DQGj4Z/amRndasXpUb5wTtMb9V6jN4pRfgw+C5ska9o5zFrIGJF0
GbSe1VVUedJ1LH3US3a79eVGmyAwcfTRMNhn+e+uYky2VYCQIEGGQ8rZAM3TveoT
N8J7Lqwtuo3DWz0IYx60DUvabpqJ+9Dl6rhTvTfyYvQK4vl2xApGf4Uo87JbNQfq
q40UXfBtMaAvIPEKCyTdOVVDrfgW0DQTl7wS+Z3p6kNm0NMI53TFTbgIuU9QiPPB
I5NdqISEPFW/HS5q0+zR1KdG4EmEjmpCX78s+uviHpHQloWQT9ov4KbXbf8y5Xso
lv+2gcd5TVjYxPRbo3SMtGRQho5uq2BNy6Q0K0//3OE+X+v+ZDi8n4MU3uA7dGGA
7uAUZOYPzNKS7ryW3h4PZIfiI5Fv9tBNnu9O3I2UH6fHNFQQLzJPCXertPmrORjP
EyCNCOhfsNwLd5Qq53cDbG1mkZro/xKDvAOx2LQcGFtmx4v1NXI204V50aSzy8vY
vQnM0gEY/YxoCq3wSjc9yeUftyv2LIgJvuXjkeHkV7gQQ+jx/HY6J7fnJGSzKMKp
/GPaPCNKvCY/72ik2gbmdvLbaRGeVJ07JO46YWEUrGb/1A==
-----END CERTIFICATE-----

View File

@ -0,0 +1,39 @@
-----BEGIN RSA PRIVATE KEY-----
MIIG5AIBAAKCAYEA9xYBDoV2tPx8lqZ/bH/wLvoPsg1/CXeknvRcNuxw1gu6c3IJ
BrKZlkFtiU6Y8FADiUBOVab/Y0cQ/9EdeB2srPH4M5KNiPdWZPgxARWnRq5Ez8pv
VASP2E2Zya1UnH5iJBau8e6SwBl8UaXnGwcA+CUv+FXcZtdoFh0Lqt3AdItQOkHV
jSE6Cfiv5lsSW0ikMcoHFOHNps4/9A4A/griT5lRDqQIycN7WD2k4+aKVreCWxbS
teU35yIDJV6PGUtw8k41arJ+kwuwYM3+YklR0Dsj0RxXn07oLqnf6IeNUogGhNVO
7RvLdpfvrhlevHVXmmYj40fkGjU15KkZOKigMw/gDInI6Sc2jp8oPX9tjkaQYkF2
t7AWOq01lh5TleMIoBFUqVcy+X/qejla0JaKCEyt/fiPUo7/SgucyFl8GrKfSdEL
UOKx5Vr2ZZ48QSfIlXle+tGtFD0AYUsO0ud0wclW5C+g8E27raTuR4RaZOj8/pmB
7XNDszwxQ/97dBRpAgMBAAECggGAcWoWPhYg8N5cScJPBvyKwOVjQvVS9IOIerXr
hgJtoLJteQRFBGACg6ewobAEH3p6xQtRaZtn6qf6M5JHFpV4Z0ICDZodgVsWuu35
gGfyCk1/pGllRIl7hWvJRXtcNSEF507KKp65mZeZKtkeBZfnZ/+Zz0GKE2KYkl3u
txVme5he0P7bCRbRTzZpdzEicegcBgaXzYwAG6rcTCgJaJKSYrsbK787kXE7MrvI
7hsqMLe3DByjx35ZdKx2CTcoNBId9RODWnPpANVrlNv7kbaZRqd5OI8b7JfblFsq
F6vCzvDq+Quc8ID1zxRZv761pexejtDzghgQy7X2EVvMlHh4//wErgq6WfPjwyvU
/zZczO0L/c1XwwkfBU6Yf6UuYCKngwifgvb7aGU4/aGNcD5SHRITwCHK/E9JrkR8
pkqerMxsf9uP5FxGdwOm1k77Lkap7Kx2Utt5l7stOY0fFUFz1YQdAHJUzhmbP3Zy
C+TeX2/9+CudXM1parW7HQRlZeMJAoHBAP545khACfRvUWpxdQohp1Ol0FuDosYg
NC75q12T8ovllx8Qly3aafJdd0NTvFmrBkBPTL3pCUWCyGZh6/E00fUL4dtD3zwz
QUbm6hWGTgKHdeLLdae2wxcZ/NqmTvpY9o/p4jS9+StRKQtdsftLKCmRv7wfYkju
UT7O+gRyGat/Rqpr9cTSKBXHUT+WJlITDrwk5QdydF7eKzLT8DROgcRRE1+FMJkj
pO5ChuAxZr0Q0fISRm9Lu7aJ3H8QFfboGwKBwQD4kcCkZvRdz8BQsOsyHQ3SlGhx
5nwA7SPadXtfnpoW0ZlEdHwkPJzU1Z50z1ulEQymBTARPUQ4s28MQt8NXuRzHBrW
PMUGgsspzT6FjiskhUc8k9PAZbEJE/axLKK2qSKktGuZj+VFih/9XPPTX4xSzlOg
ntJEr2tc3TIv+JEOuJX6VT2URFLXgdOHXxAejS0DTGIg1aB4VGQpWzfbcJ6Cyf11
YyoyYWA25wdw7sB9kDHsd0Ej0mld5+l8JOd8hcsCgcA9jCpOcUa3GzF66EQhljAt
WB6D89urxeA5OGPNN1pjob0iY1XdXkVfvGF7JEaa/XV+mm96Q2HdsRsdQDPb3CWn
+h6/dLQKkG8KYhFd8WTu0aqelw026kpXTQ7OJ4lUna3M8wmmLgiVBIVD3X6NxAjL
vRe9vW19LD70TQVFi/9PbnI+B+yilR3i3pl1IrDUCw32TYojefhRdbTHD2G6lP5n
6CAia0ls0KU0h1yt3uT1d5r/zJHCm3OkW8W76b0WQd8CgcEAh0czk4WgiomtPXz7
k3tycV9pdEuewxZMQ/FaIpD7hV2uzy2h/kqqg756jVHoq24a9yOtpEQ2o7Erx32B
TRKOvALYrC3IgKGgFfDojODxo9+RBGvjezsc3TbrNEN5jnWAMCkswhcpDO5+OHJl
FG1UviAiLTEieFUL1i9fx/G8aEmW/fV0HQQOHdE/INZgvG/Sxo/Ee+AnhDVRiZxm
StwAuGdbtI4ygday+U5Eo3acdfmK4gmI/wjdZUj4riKbhQ5/AoHBAI0yzo+PIFi6
HjNYVoC7rZ39oQ0YCrEWrui+DRdEjnjec31Jw02AtKnv5swpDDHjgnIcd9ciQY48
rk7eC6IkVrL9hOxUzC9YQZX/2MBiOLjUkDkSLt+d5PL0OXiSg1O4fGJdGiVPF0Fc
sF9p1UNEfGvXjzUB3ay0kMyCLitNe1BCvJlYXdSV9YmAMNvguE7TNU3OPiVv65PK
6OndznX41Pw7OlnLaq1sFQcYBmf5E7QSKYP+4HeV89Sc824VlCNxwA==
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0C6SSsXfuse2IV8+6hSYqSPQdoQwZ5BYQnSxuKylArCrMXx8
JGHrJP6Pj7GxRmH40v9u9VwZvcrQOm8yUTuzAEf2Kd3uvXmVKJb2vc0BopsflpSE
OLEuddTSHlHgdVHylqpbzB7ZrmyXXuWTtTFEaGmPVUmWcOBOy6pc/7hZv7HkTjaH
LQu/uohic/NjO0oJaaUwds6muwTCNSmMvtvoP51pyQJeuZjYIoWnnu+/DbtZYmH4
4VbHD0U+uSNKLZa4beWqDq5ZDwQvEVkuLqL331awzgIf0a4bhP+uc1kdWXZ8V+8a
Bbqtq6g6o9HdrzgNRR+9S3EvEelCrxuWw9FQ3QIDAQABAoIBAQDFQ5vzplQ9lIgM
T0g6XpHZk8oww0lqmOhI8HKG33Dsf6N4HNE1WGOMhnpaWrH0U1mH9eqaLE9n/Aob
lMpFFyCin42uVlGm0NJ5x7K+Xsex4POpp8kyPxIbLTJ88HCUOrZ39a1OWd1C3jsA
/OFdy/VaSsw6sKQRCTsg2amN1o2UibDJYVW47ycv9cwjk/GEzzOSq32a9o6g6Gwd
g3ycroIaxhDlGjS5l0IZ/ozhN+AS5dYcPgJRsYD/jTBqTSzIW2ePrcheznoRcgLK
bb+UVQC+PZX8kycCcerPbcGc2YcBpZgmIkCj85+ITFt/BhH7+TSH9G7F8LTKAaJg
qlYKF14BAoGBAPz8Jx0vAcv/4zIfCckuNy3kVu4PHBTMTBO5+tUg6CZgktRrroiV
+Zq1lCuj2/Px3Lx9oaUie52iV5xgmEEax77xa1rVezY1PhGSFmngHqfumUJf8EEB
snlAUpwBHvWU9B9OxKOHRrD9Y9ptXcBK30ZHLJT4t5JvbHVrKZF2J82hAoGBANKp
ue+dOafhgc1F/ThD2VLuIi6Garf1pqNG3OMugMfieHAmr1RRYWwFErLoijt9dpe9
gXVecUm1KO4/0ZkR+7YDzUSifXvcizaw+XqjrtFerrz+Yao4gZssFnw/sLc2pbWm
1DHWxRnmh6MyHEEiA0KxElgutswhP8GIKN7INOG9AoGAR1sD2Upp8lVBiuCQTQtZ
CvutvUXLwN4C00mQw06dzD1PDNU2jFXo6kcu/MQiBQOCJDQ3RLGeNk8U8QmZyDs6
fdPwWNWABEEuOZx/7+sEGo/E8KDIzj0hTuvioRf72H7kAHSiKBG+0asW4AQa/mLf
6R2oKHiipo4BBHluZxXxkiECgYEAuYXnzfH0+LhMi+77VjXKipJVYAvYqDGak2iw
1xH5MA9uabZn6iXRWkQNd6n7MvEHJBMsk6ScuIDmjwt9FwUTW/R1LeC8CfzsTToG
O88zAggUczTD5hjlazakhr/AbVmfDh7h+RJferPe+AYFhAbkQDOZKDfbnGIbt+Cl
va0rhTECgYAFb38TvJmEIzB1/nZ7sKbFmr2pYgzBqspQcprws6gZlWydd4OoTZiv
QzSBDi3tGt07yJuntVlbuI6qejhFMmonGZuntNTvTZMmx2+W/F8EGByfWpLtB9W5
S+tx5/0d4MhOYHlt0EcdC7j881swY9LCrc/EOqg1O4BlTJ5+UJer+Q==
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDfDCCAwKgAwIBAgIIUYJhG37C300wCgYIKoZIzj0EAwMwgYwxCzAJBgNVBAYT
AlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJlMRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2lu
ZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9y
bmlhMR0wGwYDVQQDExRjbG91ZGZsYXJlLWludGVyLmNvbTAeFw0xNDA0MTEyMTIy
MzhaFw0xOTA0MTEyMTI3MzhaMIGLMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQ2xv
dWRGbGFyZTEcMBoGA1UECxMTU3lzdGVtcyBFbmdpbmVlcmluZzEWMBQGA1UEBxMN
U2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5pYTEcMBoGA1UEAxMTY2xv
dWRmbGFyZS1sZWFmLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ANAukkrF37rHtiFfPuoUmKkj0HaEMGeQWEJ0sbispQKwqzF8fCRh6yT+j4+xsUZh
+NL/bvVcGb3K0DpvMlE7swBH9ind7r15lSiW9r3NAaKbH5aUhDixLnXU0h5R4HVR
8paqW8we2a5sl17lk7UxRGhpj1VJlnDgTsuqXP+4Wb+x5E42hy0Lv7qIYnPzYztK
CWmlMHbOprsEwjUpjL7b6D+dackCXrmY2CKFp57vvw27WWJh+OFWxw9FPrkjSi2W
uG3lqg6uWQ8ELxFZLi6i999WsM4CH9GuG4T/rnNZHVl2fFfvGgW6rauoOqPR3a84
DUUfvUtxLxHpQq8blsPRUN0CAwEAAaOBgTB/MA4GA1UdDwEB/wQEAwIApDASBgNV
HRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBShnoK2Oquaq/XjlNBMxs5yPTSJvjAf
BgNVHSMEGDAWgBRB+YoiUjIm34/wBwHdJGE4Wufs/DAZBgNVHREEEjAQgg5jZnNz
bC1sZWFmLmNvbTAKBggqhkjOPQQDAwNoADBlAjAhMWEJzBwuN5bVACPCAoVPSWI2
+0DQi4Tu6sBNQl+dsyO+FPyA3+aYc0NgnBwcj+0CMQC7JOdfdWJPZj6rOAXvGV3I
jGJRHZmu5q5K+9teIK1b9mustpnDJgniKAHtBGecXy4=
-----END CERTIFICATE-----

View File

@ -0,0 +1,75 @@
// Package auth implements an interface for providing CFSSL
// authentication. This is meant to authenticate a client CFSSL to a
// remote CFSSL in order to prevent unauthorised use of the signature
// capabilities. This package provides both the interface and a
// standard HMAC-based implementation.
package auth
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
// An AuthenticatedRequest contains a request and authentication
// token. The Provider may determine whether to validate the timestamp
// and remote address.
type AuthenticatedRequest struct {
// An Authenticator decides whether to use this field.
Timestamp int64 `json:"timestamp,omitempty"`
RemoteAddress []byte `json:"remote_address,omitempty"`
Token []byte `json:"token"`
Request []byte `json:"request"`
}
// A Provider can generate tokens from a request and verify a
// request. The handling of additional authentication data (such as
// the IP address) is handled by the concrete type, as is any
// serialisation and state-keeping.
type Provider interface {
Token(req []byte) (token []byte, err error)
Verify(aReq *AuthenticatedRequest) bool
}
// Standard implements an HMAC-SHA-256 authentication provider. It may
// be supplied additional data at creation time that will be used as
// request || additional-data with the HMAC.
type Standard struct {
key []byte
ad []byte
}
// New generates a new standard authentication provider from the key
// and additional data. The additional data will be used when
// generating a new token.
func New(key string, ad []byte) (*Standard, error) {
keyBytes, err := hex.DecodeString(key)
if err != nil {
return nil, err
}
return &Standard{keyBytes, ad}, nil
}
// Token generates a new authentication token from the request.
func (p Standard) Token(req []byte) (token []byte, err error) {
h := hmac.New(sha256.New, p.key)
h.Write(req)
h.Write(p.ad)
return h.Sum(nil), nil
}
// Verify determines whether an authenticated request is valid.
func (p Standard) Verify(ad *AuthenticatedRequest) bool {
if ad == nil {
return false
}
// Standard token generation returns no error.
token, _ := p.Token(ad.Request)
if len(ad.Token) != len(token) {
return false
}
return hmac.Equal(token, ad.Token)
}

View File

@ -0,0 +1,159 @@
package auth
import (
"encoding/json"
"io/ioutil"
"testing"
)
var (
testProvider Provider
testProviderAD Provider
testKey = "0123456789ABCDEF0123456789ABCDEF"
testAD = []byte{1, 2, 3, 4} // IP address 1.2.3.4
)
func TestNew(t *testing.T) {
_, err := New("ABC", nil)
if err == nil {
t.Fatal("expected failure with improperly-hex-encoded key")
}
testProvider, err = New(testKey, nil)
if err != nil {
t.Fatalf("%v", err)
}
testProviderAD, err = New(testKey, testAD)
if err != nil {
t.Fatalf("%v", err)
}
}
var (
testRequest1A = &AuthenticatedRequest{
Request: []byte(`testing 1 2 3`),
}
testRequest1B = &AuthenticatedRequest{
Request: []byte(`testing 1 2 3`),
}
testRequest2 = &AuthenticatedRequest{
Request: []byte(`testing 3 2 1`),
}
)
// Sanity check: can a newly-generated token be verified?
func TestVerifyTrue(t *testing.T) {
var err error
testRequest1A.Token, err = testProvider.Token(testRequest1A.Request)
if err != nil {
t.Fatalf("%v", err)
}
testRequest1B.Token, err = testProviderAD.Token(testRequest1B.Request)
if err != nil {
t.Fatalf("%v", err)
}
if !testProvider.Verify(testRequest1A) {
t.Fatal("failed to verify request 1A")
}
if !testProviderAD.Verify(testRequest1B) {
t.Fatal("failed to verify request 1B")
}
}
// Sanity check: ensure that additional data is actually used in
// verification.
func TestVerifyAD(t *testing.T) {
if testProvider.Verify(testRequest1B) {
t.Fatal("no-AD provider verifies request with AD")
}
if testProviderAD.Verify(testRequest1A) {
t.Fatal("AD provider verifies request without AD")
}
}
// Sanity check: verification fails if tokens are not the same length.
func TestTokenLength(t *testing.T) {
token := testRequest1A.Token[:]
testRequest1A.Token = testRequest1A.Token[1:]
if testProvider.Verify(testRequest1A) {
t.Fatal("invalid token should not be verified")
}
testRequest1A.Token = token
}
// Sanity check: token fails validation if the request is changed.
func TestBadRequest(t *testing.T) {
testRequest2.Token = testRequest1A.Token
if testProvider.Verify(testRequest2) {
t.Fatal("bad request should fail verification")
}
}
// Sanity check: a null request should fail to verify.
func TestNullRequest(t *testing.T) {
if testProvider.Verify(nil) {
t.Fatal("null request should fail verification")
}
}
// Sanity check: verify a pre-generated authenticated request.
func TestPreGenerated(t *testing.T) {
in, err := ioutil.ReadFile("testdata/authrequest.json")
if err != nil {
t.Fatalf("%v", err)
}
var req AuthenticatedRequest
err = json.Unmarshal(in, &req)
if err != nil {
t.Fatalf("%v", err)
}
if !testProvider.Verify(&req) {
t.Fatal("failed to verify pre-generated request")
}
}
var bmRequest []byte
func TestLoadBenchmarkRequest(t *testing.T) {
in, err := ioutil.ReadFile("testdata/request.json")
if err != nil {
t.Fatalf("%v", err)
}
bmRequest = in
}
func BenchmarkToken(b *testing.B) {
for i := 0; i < b.N; i++ {
_, err := testProvider.Token(bmRequest)
if err != nil {
b.Fatalf("%v", err)
}
}
}
func BenchmarkVerify(b *testing.B) {
token, _ := testProvider.Token(bmRequest)
req := &AuthenticatedRequest{
Token: token,
Request: bmRequest,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if !testProvider.Verify(req) {
b.Fatal("failed to verify request")
}
}
}

View File

@ -0,0 +1 @@
{"token": "tSU1WTE/322iXrOBfJSQ9/u1dleqpwUmCj1LXYHw07Y=", "request": "ewoJImhvc3RuYW1lIjogImt5bGVpc29tLm5ldCIsCgkicmVxdWVzdCI6ICItLS0tLUJFR0lOIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLQoJICAgIE1JSUQwVENDQWpzQ0FRQXdZREVMTUFrR0ExVUVCaE1DVlZNeEVqQVFCZ05WQkFvVENXUnliM0J6YjI1a1pURVEKCSAgICBNQTRHQTFVRUN4TUhRMFl0UTJoaGRERVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeVlXNWphWE5qYnpFVE1CRUdBMVVFCgkgICAgQ0JNS1EyRnNhV1p2Y201cFlUQ0NBYUl3RFFZSktvWklodmNOQVFFQkJRQURnZ0dQQURDQ0FZb0NnZ0dCQU1jQwoJICAgIEdCbDVMVHJla0dGV2hvdGtkYlorUjFNbG9hcld4UXY5alA0QWVrdDhVT2ljeXBIdkZPNnhPdFN3SG8rcjMyaUUKCSAgICBxblM1eXYvMDFQMk1KdXlxbmRuY1RTTXNPbFQvN242N1RNMDB1MDFLLzljL3NvZ0tFS2pseXBsVFA3eUZkRy9jCgkgICAgT3UvOXFLYi9KYWxkMndFTEZZRTZ4cTJSREZ5eHlpWk9CM2c3WjdGeGE1ZDZhZGZHUndaek50VUw0LzhzK0x5aQoJICAgIHFkdzlJMWZrUWQ2MDRwb1pGTjB3clFzNGxmaFdUVWZnMHJIdWg1d2dHS1AzVnpacGJ0OEZiMXZOamZiSHRvaHgKCSAgICBHMlBDVTZKeStEYzFiU2ZVeldjUW5lbnA4NThXNEY4ejdwRjV5YmRuRlIzMTNIam9zcVhuRzI4eklUck9hZE1UCgkgICAgSGFKNnpPaGdFYWZVT1dYT3pqTm9mRkJGYTJJdUNBVCtJVFJZMXRDL2dxcHhHd0gveXVWTjE5Qkc4VXBuMCtIQQoJICAgIGllMm1LQ0hmU0JBS1QvWGU0dW1QZWF4U2JJcVdzVzhjaytkM2I0b3I5Ulp2NWNaUmNUM29pa0p0K1NRRzY5cFcKCSAgICA0T0FiYitBQnNzL05JdXJpNnowZTdERWVJTDV6bXlTSnFkdFlIZE5ZTjcrK3Y5eEJOc0w0SXNVNklFeTMrUUlECgkgICAgQVFBQm9DNHdMQVlKS29aSWh2Y05BUWtPTVI4d0hUQWJCZ05WSFJFRUZEQVNnaEJqWmk1a2NtOXdjMjl1WkdVdQoJICAgIGJtVjBNQXNHQ1NxR1NJYjNEUUVCREFPQ0FZRUFoTUFxQmlySStrMWFVM2xmQUdRaVNtOHl0T3paaWozODloSXIKCSAgICBuVXA4K1duVHVWVGI4WFozL1YrTDlFblRJbUY2dTF3ZWFqWGQzU3VlNDk1NzBMYlltSXV4QmtHcDUwL0JkVUR6CgkgICAgdUI2eHNoaEpXczEySnhVYjkxSW1tMGJUUncyek1xZXdnYTZmdHpaL0FLNG1zeFFBMlVJYmNXWmRzS2J1TTdzbwoJICAgIEpUZlZXOWlPd3FIdC82NFpqNHRCWmY5THpPRHI3a051S0tMbndqaXpIMTg3eGZJSWhkcmpGOFdTN0g5QVBCMU8KCSAgICBTdUVVRGZxaDBTV1IzbHRXdUF1VVdlbzZTS2NIVnVzeS9HNFlFK1BCeXcxZVY3RzRTYmVHNVowbytHT1VVSy9GCgkgICAgYjU1R21XMXhhNExBcnMxQSt6ZUZidkovQkFwc2JVMmI2V1ZtTmE3V3BIejdXWElGT0p1WUpnRWtWS1BKbkt1cwoJICAgIHFxczNGZ1VxejBadjdUSzhtTWlFVEpvWFpzNnpDdk15c1FldTNKL29qZ3RBanZNaHpRYzZQUy9udk90SmRJZysKCSAgICBIMHFYNDlmaHAxQnJZeXNsYWx6UUlGMCtIMHFTVWV5b1V5VjJ3YkxCQUxhcHhNZnZUVmxoTnduYWN0Y0tReHE0CgkgICAgK3dUKzJQVEowYk0vNUFWMFRPMVNQVDBBVmlKaAoJICAgIC0tLS0tRU5EIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLSIsCgkicHJvZmlsZSI6ICIiLAoJInJlbW90ZSI6ICIiLAoJImxhYmVsIjogInByaW1hcnkiCn0KCg=="}

View File

@ -0,0 +1,30 @@
{
"hostname": "kyleisom.net",
"request": "-----BEGIN CERTIFICATE REQUEST-----
MIID0TCCAjsCAQAwYDELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCWRyb3Bzb25kZTEQ
MA4GA1UECxMHQ0YtQ2hhdDEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UE
CBMKQ2FsaWZvcm5pYTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAMcC
GBl5LTrekGFWhotkdbZ+R1MloarWxQv9jP4Aekt8UOicypHvFO6xOtSwHo+r32iE
qnS5yv/01P2MJuyqndncTSMsOlT/7n67TM00u01K/9c/sogKEKjlyplTP7yFdG/c
Ou/9qKb/Jald2wELFYE6xq2RDFyxyiZOB3g7Z7Fxa5d6adfGRwZzNtUL4/8s+Lyi
qdw9I1fkQd604poZFN0wrQs4lfhWTUfg0rHuh5wgGKP3VzZpbt8Fb1vNjfbHtohx
G2PCU6Jy+Dc1bSfUzWcQnenp858W4F8z7pF5ybdnFR313HjosqXnG28zITrOadMT
HaJ6zOhgEafUOWXOzjNofFBFa2IuCAT+ITRY1tC/gqpxGwH/yuVN19BG8Upn0+HA
ie2mKCHfSBAKT/Xe4umPeaxSbIqWsW8ck+d3b4or9RZv5cZRcT3oikJt+SQG69pW
4OAbb+ABss/NIuri6z0e7DEeIL5zmySJqdtYHdNYN7++v9xBNsL4IsU6IEy3+QID
AQABoC4wLAYJKoZIhvcNAQkOMR8wHTAbBgNVHREEFDASghBjZi5kcm9wc29uZGUu
bmV0MAsGCSqGSIb3DQEBDAOCAYEAhMAqBirI+k1aU3lfAGQiSm8ytOzZij389hIr
nUp8+WnTuVTb8XZ3/V+L9EnTImF6u1weajXd3Sue49570LbYmIuxBkGp50/BdUDz
uB6xshhJWs12JxUb91Imm0bTRw2zMqewga6ftzZ/AK4msxQA2UIbcWZdsKbuM7so
JTfVW9iOwqHt/64Zj4tBZf9LzODr7kNuKKLnwjizH187xfIIhdrjF8WS7H9APB1O
SuEUDfqh0SWR3ltWuAuUWeo6SKcHVusy/G4YE+PByw1eV7G4SbeG5Z0o+GOUUK/F
b55GmW1xa4LArs1A+zeFbvJ/BApsbU2b6WVmNa7WpHz7WXIFOJuYJgEkVKPJnKus
qqs3FgUqz0Zv7TK8mMiETJoXZs6zCvMysQeu3J/ojgtAjvMhzQc6PS/nvOtJdIg+
H0qX49fhp1BrYyslalzQIF0+H0qSUeyoUyV2wbLBALapxMfvTVlhNwnactcKQxq4
+wT+2PTJ0bM/5AV0TO1SPT0AViJh
-----END CERTIFICATE REQUEST-----",
"profile": "",
"remote": "",
"label": "primary"
}

View File

@ -0,0 +1,419 @@
// Package config contains the configuration logic for CF-SSL.
package config
import (
"crypto/x509"
"encoding/asn1"
"encoding/json"
"errors"
"io/ioutil"
"regexp"
"strconv"
"strings"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/auth"
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/log"
)
// A SigningProfile stores information that the CA needs to store
// signature policy.
type SigningProfile struct {
Usage []string `json:"usages"`
IssuerURL []string `json:"issuer_urls"`
OCSP string `json:"ocsp_url"`
CRL string `json:"crl_url"`
CA bool `json:"is_ca"`
PolicyStrings []string `json:"policies"`
OCSPNoCheck bool `json:"ocsp_no_check"`
ExpiryString string `json:"expiry"`
BackdateString string `json:"backdate"`
AuthKeyName string `json:"auth_key"`
RemoteName string `json:"remote"`
NotBefore time.Time `json:"not_before"`
NotAfter time.Time `json:"not_after"`
Policies []asn1.ObjectIdentifier
Expiry time.Duration
Backdate time.Duration
Provider auth.Provider
RemoteServer string
}
func parseObjectIdentifier(oidString string) (oid asn1.ObjectIdentifier, err error) {
validOID, err := regexp.MatchString("\\d(\\.\\d+)*", oidString)
if err != nil {
return
}
if !validOID {
err = errors.New("Invalid OID")
return
}
segments := strings.Split(oidString, ".")
oid = make(asn1.ObjectIdentifier, len(segments))
for i, intString := range segments {
oid[i], err = strconv.Atoi(intString)
if err != nil {
return
}
}
return
}
const timeFormat = "2006-01-02T15:04:05"
// populate is used to fill in the fields that are not in JSON
//
// First, the ExpiryString parameter is needed to parse
// expiration timestamps from JSON. The JSON decoder is not able to
// decode a string time duration to a time.Duration, so this is called
// when loading the configuration to properly parse and fill out the
// Expiry parameter.
// This function is also used to create references to the auth key
// and default remote for the profile.
// It returns true if ExpiryString is a valid representation of a
// time.Duration, and the AuthKeyString and RemoteName point to
// valid objects. It returns false otherwise.
func (p *SigningProfile) populate(cfg *Config) error {
if p == nil {
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("can't parse nil profile"))
}
var err error
if p.RemoteName == "" {
log.Debugf("parse expiry in profile")
if p.ExpiryString == "" {
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("empty expiry string"))
}
dur, err := time.ParseDuration(p.ExpiryString)
if err != nil {
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err)
}
log.Debugf("expiry is valid")
p.Expiry = dur
if p.BackdateString != "" {
dur, err = time.ParseDuration(p.BackdateString)
if err != nil {
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err)
}
p.Backdate = dur
}
if !p.NotBefore.IsZero() && !p.NotAfter.IsZero() && p.NotAfter.Before(p.NotBefore) {
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err)
}
if len(p.PolicyStrings) > 0 {
p.Policies = make([]asn1.ObjectIdentifier, len(p.PolicyStrings))
for i, oidString := range p.PolicyStrings {
p.Policies[i], err = parseObjectIdentifier(oidString)
if err != nil {
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err)
}
}
}
} else {
log.Debug("match remote in profile to remotes section")
if remote := cfg.Remotes[p.RemoteName]; remote != "" {
if err := p.updateRemote(remote); err != nil {
return err
}
} else {
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
errors.New("failed to find remote in remotes section"))
}
}
if p.AuthKeyName != "" {
log.Debug("match auth key in profile to auth_keys section")
if key, ok := cfg.AuthKeys[p.AuthKeyName]; ok == true {
if key.Type == "standard" {
p.Provider, err = auth.New(key.Key, nil)
if err != nil {
log.Debugf("failed to create new standard auth provider: %v", err)
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
errors.New("failed to create new standard auth provider"))
}
} else {
log.Debugf("unknown authentication type %v", key.Type)
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
errors.New("unknown authentication type"))
}
} else {
return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy,
errors.New("failed to find auth_key in auth_keys section"))
}
}
return nil
}
// updateRemote takes a signing profile and initializes the remote server object
// to the hostname:port combination sent by remote
func (p *SigningProfile) updateRemote(remote string) error {
if remote != "" {
p.RemoteServer = remote
}
return nil
}
// OverrideRemotes takes a signing configuration and updates the remote server object
// to the hostname:port combination sent by remote
func (p *Signing) OverrideRemotes(remote string) error {
if remote != "" {
var err error
for _, profile := range p.Profiles {
err = profile.updateRemote(remote)
if err != nil {
return err
}
}
err = p.Default.updateRemote(remote)
if err != nil {
return err
}
}
return nil
}
// NeedsRemoteSigner returns true if one of the profiles has a remote set
func (p *Signing) NeedsRemoteSigner() bool {
for _, profile := range p.Profiles {
if profile.RemoteName != "" {
return true
}
}
if p.Default.RemoteName != "" {
return true
}
return false
}
// NeedsLocalSigner returns true if one of the profiles doe not have a remote set
func (p *Signing) NeedsLocalSigner() bool {
for _, profile := range p.Profiles {
if profile.RemoteName == "" {
return true
}
}
if p.Default.RemoteName == "" {
return true
}
return false
}
// Usages parses the list of key uses in the profile, translating them
// to a list of X.509 key usages and extended key usages. The unknown
// uses are collected into a slice that is also returned.
func (p *SigningProfile) Usages() (ku x509.KeyUsage, eku []x509.ExtKeyUsage, unk []string) {
for _, keyUse := range p.Usage {
if kuse, ok := KeyUsage[keyUse]; ok {
ku |= kuse
} else if ekuse, ok := ExtKeyUsage[keyUse]; ok {
eku = append(eku, ekuse)
} else {
unk = append(unk, keyUse)
}
}
return
}
// A valid profile must be a valid local profile or a valid remote profile.
// A valid local profile has defined at least key usages to be used, and a
// valid local default profile has defined at least a default expiration.
// A valid remote profile (default or not) has remote signer initialized.
// In addition, a remote profile must has a valid auth provider if auth
// key defined.
func (p *SigningProfile) validProfile(isDefault bool) bool {
if p == nil {
return false
}
if p.RemoteName != "" {
log.Debugf("validate remote profile")
if p.RemoteServer == "" {
log.Debugf("invalid remote profile: no remote signer specified")
return false
}
if p.AuthKeyName != "" && p.Provider == nil {
log.Debugf("invalid remote profile: auth key name is defined but no auth provider is set")
return false
}
} else {
log.Debugf("validate local profile")
if !isDefault {
if len(p.Usage) == 0 {
log.Debugf("invalid local profile: no usages specified")
return false
} else if _, _, unk := p.Usages(); len(unk) == len(p.Usage) {
log.Debugf("invalid local profile: no valid usages")
return false
}
} else {
if p.Expiry == 0 {
log.Debugf("invalid local profile: no expiry set")
return false
}
}
}
log.Debugf("profile is valid")
return true
}
// Signing codifies the signature configuration policy for a CA.
type Signing struct {
Profiles map[string]*SigningProfile `json:"profiles"`
Default *SigningProfile `json:"default"`
}
// Config stores configuration information for the CA.
type Config struct {
Signing *Signing `json:"signing"`
AuthKeys map[string]AuthKey `json:"auth_keys,omitempty"`
Remotes map[string]string `json:"remotes,omitempty"`
}
// Valid ensures that Config is a valid configuration. It should be
// called immediately after parsing a configuration file.
func (c *Config) Valid() bool {
return c.Signing.Valid()
}
// Valid checks the signature policies, ensuring they are valid
// policies. A policy is valid if it has defined at least key usages
// to be used, and a valid default profile has defined at least a
// default expiration.
func (p *Signing) Valid() bool {
if p == nil {
return false
}
log.Debugf("validating configuration")
if !p.Default.validProfile(true) {
log.Debugf("default profile is invalid")
return false
}
for _, sp := range p.Profiles {
if !sp.validProfile(false) {
log.Debugf("invalid profile")
return false
}
}
return true
}
// KeyUsage contains a mapping of string names to key usages.
var KeyUsage = map[string]x509.KeyUsage{
"signing": x509.KeyUsageDigitalSignature,
"digital signature": x509.KeyUsageDigitalSignature,
"content committment": x509.KeyUsageContentCommitment,
"key encipherment": x509.KeyUsageKeyEncipherment,
"data encipherment": x509.KeyUsageDataEncipherment,
"cert sign": x509.KeyUsageCertSign,
"crl sign": x509.KeyUsageCRLSign,
"encipher only": x509.KeyUsageEncipherOnly,
"decipher only": x509.KeyUsageDecipherOnly,
}
// ExtKeyUsage contains a mapping of string names to extended key
// usages.
var ExtKeyUsage = map[string]x509.ExtKeyUsage{
"any": x509.ExtKeyUsageAny,
"server auth": x509.ExtKeyUsageServerAuth,
"client auth": x509.ExtKeyUsageClientAuth,
"code signing": x509.ExtKeyUsageCodeSigning,
"email protection": x509.ExtKeyUsageEmailProtection,
"s/mime": x509.ExtKeyUsageEmailProtection,
"ipsec end system": x509.ExtKeyUsageIPSECEndSystem,
"ipsec tunnel": x509.ExtKeyUsageIPSECTunnel,
"ipsec user": x509.ExtKeyUsageIPSECUser,
"timestamping": x509.ExtKeyUsageTimeStamping,
"ocsp signing": x509.ExtKeyUsageOCSPSigning,
"microsoft sgc": x509.ExtKeyUsageMicrosoftServerGatedCrypto,
"netscape sgc": x509.ExtKeyUsageNetscapeServerGatedCrypto,
}
// An AuthKey contains an entry for a key used for authentication.
type AuthKey struct {
// Type contains information needed to select the appropriate
// constructor. For example, "standard" for HMAC-SHA-256,
// "standard-ip" for HMAC-SHA-256 incorporating the client's
// IP.
Type string `json:"type"`
// Key contains the key information, such as a hex-encoded
// HMAC key.
Key string `json:"key"`
}
// DefaultConfig returns a default configuration specifying basic key
// usage and a 1 year expiration time. The key usages chosen are
// signing, key encipherment, client auth and server auth.
func DefaultConfig() *SigningProfile {
d := helpers.OneYear
return &SigningProfile{
Usage: []string{"signing", "key encipherment", "server auth", "client auth"},
Expiry: d,
ExpiryString: "8760h",
}
}
// LoadFile attempts to load the configuration file stored at the path
// and returns the configuration. On error, it returns nil.
func LoadFile(path string) (*Config, error) {
log.Debugf("loading configuration file from %s", path)
if path == "" {
return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("invalid path"))
}
body, err := ioutil.ReadFile(path)
if err != nil {
return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("could not read configuration file"))
}
return LoadConfig(body)
}
// LoadConfig attempts to load the configuration from a byte slice.
// On error, it returns nil.
func LoadConfig(config []byte) (*Config, error) {
var cfg = &Config{}
err := json.Unmarshal(config, &cfg)
if err != nil {
return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("failed to unmarshal configuration"))
}
if cfg.Signing.Default == nil {
log.Debugf("no default given: using default config")
cfg.Signing.Default = DefaultConfig()
} else {
if err := cfg.Signing.Default.populate(cfg); err != nil {
return nil, err
}
}
for k := range cfg.Signing.Profiles {
if err := cfg.Signing.Profiles[k].populate(cfg); err != nil {
return nil, err
}
}
if !cfg.Valid() {
return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("invalid configuration"))
}
log.Debugf("configuration ok")
return cfg, nil
}

View File

@ -0,0 +1,337 @@
package config
import (
"encoding/json"
"fmt"
"testing"
"time"
)
var expiry = 1 * time.Minute
var invalidProfileConfig = &Config{
Signing: &Signing{
Profiles: map[string]*SigningProfile{
"invalid": {
Usage: []string{"wiretapping"},
Expiry: expiry,
},
"empty": {},
},
Default: &SigningProfile{
Usage: []string{"digital signature"},
Expiry: expiry,
},
},
}
var invalidDefaultConfig = &Config{
Signing: &Signing{
Profiles: map[string]*SigningProfile{
"key usage": {
Usage: []string{"digital signature"},
},
},
Default: &SigningProfile{
Usage: []string{"s/mime"},
},
},
}
var validConfig = &Config{
Signing: &Signing{
Profiles: map[string]*SigningProfile{
"valid": {
Usage: []string{"digital signature"},
Expiry: expiry,
},
},
Default: &SigningProfile{
Usage: []string{"digital signature"},
Expiry: expiry,
},
},
}
var validMixedConfig = `
{
"signing": {
"profiles": {
"CA": {
"auth_key": "sample",
"remote": "localhost"
},
"email": {
"usages": ["s/mime"],
"expiry": "720h"
}
},
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "8000h"
}
},
"auth_keys": {
"sample": {
"type":"standard",
"key":"0123456789ABCDEF0123456789ABCDEF"
}
},
"remotes": {
"localhost": "127.0.0.1:8888"
}
}`
var validMinimalRemoteConfig = `
{
"signing": {
"default": {
"auth_key": "sample",
"remote": "localhost"
}
},
"auth_keys": {
"sample": {
"type":"standard",
"key":"0123456789ABCDEF0123456789ABCDEF"
}
},
"remotes": {
"localhost": "127.0.0.1:8888"
}
}`
var validMinimalLocalConfig = `
{
"signing": {
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "8000h"
}
}
}`
func TestInvalidProfile(t *testing.T) {
if invalidProfileConfig.Signing.Profiles["invalid"].validProfile(false) {
t.Fatal("invalid profile accepted as valid")
}
if invalidProfileConfig.Signing.Profiles["empty"].validProfile(false) {
t.Fatal("invalid profile accepted as valid")
}
if invalidProfileConfig.Valid() {
t.Fatal("invalid config accepted as valid")
}
if !invalidProfileConfig.Signing.Profiles["invalid"].validProfile(true) {
t.Fatal("invalid profile should be a valid default profile")
}
}
func TestRemoteProfiles(t *testing.T) {
var validRemoteProfile = &SigningProfile{
RemoteName: "localhost",
RemoteServer: "localhost:8080",
}
var invalidRemoteProfile = &SigningProfile{
RemoteName: "localhost",
}
var invalidRemoteAuthProfile = &SigningProfile{
RemoteName: "localhost",
RemoteServer: "localhost:8080",
AuthKeyName: "blahblah",
}
if !validRemoteProfile.validProfile(true) ||
!validRemoteProfile.validProfile(false) {
t.Fatal("valid remote profile is rejected.")
}
if invalidRemoteProfile.validProfile(true) ||
invalidRemoteProfile.validProfile(false) {
t.Fatal("invalid remote profile is accepted.")
}
if invalidRemoteAuthProfile.validProfile(true) ||
invalidRemoteAuthProfile.validProfile(false) {
t.Fatal("invalid remote profile is accepted.")
}
}
func TestInvalidDefault(t *testing.T) {
if invalidDefaultConfig.Signing.Default.validProfile(true) {
t.Fatal("invalid default accepted as valid")
}
if invalidDefaultConfig.Valid() {
t.Fatal("invalid config accepted as valid")
}
if !invalidDefaultConfig.Signing.Default.validProfile(false) {
t.Fatal("invalid default profile should be a valid profile")
}
}
func TestValidConfig(t *testing.T) {
if !validConfig.Valid() {
t.Fatal("Valid config is not valid")
}
bytes, _ := json.Marshal(validConfig)
fmt.Printf("%v", string(bytes))
}
func TestDefaultConfig(t *testing.T) {
if !DefaultConfig().validProfile(false) {
t.Fatal("global default signing profile should be a valid profile.")
}
if !DefaultConfig().validProfile(true) {
t.Fatal("global default signing profile should be a valid default profile")
}
}
func TestParse(t *testing.T) {
var validProfiles = []*SigningProfile{
{
ExpiryString: "8760h",
},
{
ExpiryString: "168h",
},
{
ExpiryString: "300s",
},
}
var invalidProfiles = []*SigningProfile{
nil,
{},
{
ExpiryString: "",
},
{
ExpiryString: "365d",
},
{
ExpiryString: "1y",
},
{
ExpiryString: "one year",
},
}
for _, p := range validProfiles {
if p.populate(nil) != nil {
t.Fatalf("Failed to parse ExpiryString=%s", p.ExpiryString)
}
}
for _, p := range invalidProfiles {
if p.populate(nil) == nil {
if p != nil {
t.Fatalf("ExpiryString=%s should not be parseable", p.ExpiryString)
}
t.Fatalf("Nil profile should not be parseable")
}
}
}
func TestLoadFile(t *testing.T) {
validConfigFiles := []string{
"testdata/valid_config.json",
"testdata/valid_config_auth.json",
"testdata/valid_config_no_default.json",
"testdata/valid_config_auth_no_default.json",
}
for _, configFile := range validConfigFiles {
_, err := LoadFile(configFile)
if err != nil {
t.Fatal("Load valid config file failed.", configFile, "error is ", err)
}
}
}
func TestLoadInvalidConfigFile(t *testing.T) {
invalidConfigFiles := []string{"", "testdata/no_such_file",
"testdata/invalid_default.json",
"testdata/invalid_profiles.json",
"testdata/invalid_usage.json",
"testdata/invalid_config.json",
"testdata/invalid_auth.json",
"testdata/invalid_auth_bad_key.json",
"testdata/invalid_no_auth_keys.json",
"testdata/invalid_remote.json",
"testdata/invalid_no_remotes.json",
}
for _, configFile := range invalidConfigFiles {
_, err := LoadFile(configFile)
if err == nil {
t.Fatal("Invalid config is loaded.", configFile)
}
}
}
func TestNeedLocalSigner(t *testing.T) {
c, err := LoadConfig([]byte(validMixedConfig))
if err != nil {
t.Fatal("load valid config failed:", err)
}
// This signing config needs both local signer and remote signer.
if c.Signing.NeedsLocalSigner() != true {
t.Fatal("incorrect NeedsLocalSigner().")
}
if c.Signing.NeedsRemoteSigner() != true {
t.Fatal("incorrect NeedsRemoteSigner()")
}
remoteConfig, err := LoadConfig([]byte(validMinimalRemoteConfig))
if err != nil {
t.Fatal("Load valid config failed:", err)
}
if remoteConfig.Signing.NeedsLocalSigner() != false {
t.Fatal("incorrect NeedsLocalSigner().")
}
if remoteConfig.Signing.NeedsRemoteSigner() != true {
t.Fatal("incorrect NeedsRemoteSigner().")
}
localConfig, err := LoadConfig([]byte(validMinimalLocalConfig))
if localConfig.Signing.NeedsLocalSigner() != true {
t.Fatal("incorrect NeedsLocalSigner().")
}
if localConfig.Signing.NeedsRemoteSigner() != false {
t.Fatal("incorrect NeedsRemoteSigner().")
}
}
func TestOverrideRemotes(t *testing.T) {
c, err := LoadConfig([]byte(validMixedConfig))
if err != nil {
t.Fatal("load valid config failed:", err)
}
host := "localhost:8888"
c.Signing.OverrideRemotes(host)
if c.Signing.Default.RemoteServer != host {
t.Fatal("should override default profile's RemoteServer")
}
for _, p := range c.Signing.Profiles {
if p.RemoteServer != host {
t.Fatal("failed to override profile's RemoteServer")
}
}
}

View File

@ -0,0 +1,27 @@
{
"signing": {
"profiles": {
"CA": {
"remote": "localhost",
"auth_key": "garbage"
},
"email": {
"usages": ["s/mime"],
"expiry": "720h"
}
},
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "8000h"
}
},
"auth_keys": {
"garbage": {
"type":"stadardo",
"key":"0123456789ABCDEF0123456789ABCDEF"
}
},
"remotes": {
"localhost": "127.0.0.1:8888"
}
}

View File

@ -0,0 +1,27 @@
{
"signing": {
"profiles": {
"CA": {
"remote": "localhost",
"auth_key": "garbage"
},
"email": {
"usages": ["s/mime"],
"expiry": "720h"
}
},
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "8000h"
}
},
"auth_keys": {
"garbage": {
"type":"standard",
"key":"BAD_KEY"
}
},
"remotes": {
"localhost": "127.0.0.1:8888"
}
}

View File

@ -0,0 +1,17 @@
{
"signing": {
"profiles": {
"CA": {
"usages": ["cert sign"],
"expiry": "720h"
},
"email": {
"usages": ["s/mime"],
"expiry": "720h"
}
},
"default": {
"usages": ["digital signature", "email protection"],
}
}
}

View File

@ -0,0 +1,18 @@
{
"signing": {
"profiles": {
"CA": {
"usages": ["cert sign"],
"expiry": "720h"
},
"email": {
"usages": ["s/mime"],
"expiry": "720h"
}
},
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "invalid_expiry"
}
}
}

View File

@ -0,0 +1,23 @@
{
"signing": {
"profiles": {
"CA": {
"remote": "localhost",
"auth_key": "garbage"
},
"email": {
"usages": ["s/mime"],
"expiry": "720h"
}
},
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "8000h"
}
},
"auth_keys": {
},
"remotes": {
"localhost": "127.0.0.1:8888"
}
}

View File

@ -0,0 +1,24 @@
{
"signing": {
"profiles": {
"CA": {
"auth_key": "garbage",
"remote": "localhoster"
},
"email": {
"usages": ["s/mime"],
"expiry": "720h"
}
},
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "8000h"
}
},
"auth_keys": {
"garbage": {
"type":"standard",
"key":"0123456789ABCDEF0123456789ABCDEF"
}
}
}

View File

@ -0,0 +1,18 @@
{
"signing": {
"profiles": {
"CA": {
"usages": ["cert sign"],
"expiry": "720h"
},
"email": {
"usages": ["s/mime"],
"expiry": "invalid_expiry"
}
},
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "8000h"
}
}
}

View File

@ -0,0 +1,27 @@
{
"signing": {
"profiles": {
"CA": {
"auth_key": "garbage",
"remote": "localhoster"
},
"email": {
"usages": ["s/mime"],
"expiry": "720h"
}
},
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "8000h"
}
},
"auth_keys": {
"garbage": {
"type":"standard",
"key":"0123456789ABCDEF0123456789ABCDEF"
}
},
"remotes": {
"localhost": "127.0.0.1:8888"
}
}

View File

@ -0,0 +1,18 @@
{
"signing": {
"profiles": {
"CA": {
"usages": ["cert sign"],
"expiry": "720h"
},
"email": {
"usages": ["BAD_USAGE"],
"expiry": "720h"
}
},
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "8000h"
}
}
}

View File

@ -0,0 +1,24 @@
{
"signing": {
"profiles": {
"CA": {
"usages": ["cert sign"],
"expiry": "720h"
},
"email": {
"usages": ["s/mime"],
"expiry": "720h"
}
},
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "8000h"
}
},
"auth_key": {
"garbage": {
"type":"standard",
"key":"0123456789ABCDEF0123456789ABCDEF"
}
}
}

View File

@ -0,0 +1,29 @@
{
"signing": {
"profiles": {
"CA": {
"usages": ["cert sign"],
"expiry": "720h",
"auth_key": "garbage",
"remote": "localhost"
},
"email": {
"usages": ["s/mime"],
"expiry": "720h"
}
},
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "8000h"
}
},
"auth_keys": {
"garbage": {
"type":"standard",
"key":"0123456789ABCDEF0123456789ABCDEF"
}
},
"remotes": {
"localhost": "127.0.0.1:8888"
}
}

View File

@ -0,0 +1,19 @@
{
"signing": {
"profiles": {
"CA": {
"auth_key": "garbage",
"remote": "localhost"
}
}
},
"auth_keys": {
"garbage": {
"type":"standard",
"key":"0123456789ABCDEF0123456789ABCDEF"
}
},
"remotes": {
"localhost": "127.0.0.1:8888"
}
}

View File

@ -0,0 +1,14 @@
{
"signing": {
"profiles": {
"CA": {
"usages": ["cert sign"],
"expiry": "720h"
},
"email": {
"usages": ["s/mime"],
"expiry": "720h"
}
}
}
}

View File

@ -0,0 +1,236 @@
// Package csr implements certificate requests for CF-SSL.
package csr
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/log"
)
const (
curveP256 = 256
curveP384 = 384
curveP521 = 521
)
// A Name contains the SubjectInfo fields.
type Name struct {
C string // Country
ST string // State
L string // Locality
O string // OrganisationName
OU string // OrganisationalUnitName
}
// A KeyRequest contains the algorithm and key size for a new private
// key.
type KeyRequest struct {
Algo string `json:"algo"`
Size int `json:"size"`
}
// The DefaultKeyRequest is used when no key request data is provided
// in the request. This should be a safe default.
var DefaultKeyRequest = KeyRequest{
Algo: "ecdsa",
Size: curveP256,
}
// Generate generates a key as specified in the request. Currently,
// only ECDSA and RSA are supported.
func (kr *KeyRequest) Generate() (interface{}, error) {
log.Debugf("generate key from request: algo=%s, size=%d", kr.Algo, kr.Size)
switch kr.Algo {
case "rsa":
if kr.Size < 2048 {
return nil, errors.New("RSA key is too weak")
}
return rsa.GenerateKey(rand.Reader, kr.Size)
case "ecdsa":
var curve elliptic.Curve
switch kr.Size {
case curveP256:
curve = elliptic.P256()
case curveP384:
curve = elliptic.P384()
case curveP521:
curve = elliptic.P521()
default:
return nil, errors.New("invalid curve")
}
return ecdsa.GenerateKey(curve, rand.Reader)
default:
return nil, errors.New("invalid algorithm")
}
}
// SigAlgo returns an appropriate X.509 signature algorithm given the
// key request's type and size.
func (kr *KeyRequest) SigAlgo() x509.SignatureAlgorithm {
switch kr.Algo {
case "rsa":
switch {
case kr.Size >= 4096:
return x509.SHA512WithRSA
case kr.Size >= 3072:
return x509.SHA384WithRSA
case kr.Size >= 2048:
return x509.SHA256WithRSA
default:
return x509.SHA1WithRSA
}
case "ecdsa":
switch {
case kr.Size == curveP521:
return x509.ECDSAWithSHA512
case kr.Size == curveP384:
return x509.ECDSAWithSHA384
case kr.Size == curveP256:
return x509.ECDSAWithSHA256
default:
return x509.ECDSAWithSHA1
}
default:
return x509.UnknownSignatureAlgorithm
}
}
// CAConfig is a section used in the requests initialising a new CA.
type CAConfig struct {
PathLength int `json:"pathlen"`
Expiry string `json:"expiry"`
}
// A CertificateRequest encapsulates the API interface to the
// certificate request functionality.
type CertificateRequest struct {
CN string
Names []Name `json:"names"`
Hosts []string `json:"hosts"`
KeyRequest *KeyRequest `json:"key,omitempty"`
CA *CAConfig `json:"ca,omitempty"`
}
// appendIf appends to a if s is not an empty string.
func appendIf(s string, a *[]string) {
if s != "" {
*a = append(*a, s)
}
}
// Name returns the PKIX name for the request.
func (cr *CertificateRequest) Name() pkix.Name {
var name pkix.Name
name.CommonName = cr.CN
for _, n := range cr.Names {
appendIf(n.C, &name.Country)
appendIf(n.ST, &name.Province)
appendIf(n.L, &name.Locality)
appendIf(n.O, &name.Organization)
appendIf(n.OU, &name.OrganizationalUnit)
}
return name
}
// ParseRequest takes a certificate request and generates a key and
// CSR from it. It does no validation -- caveat emptor. It will,
// however, fail if the key request is not valid (i.e., an unsupported
// curve or RSA key size). The lack of validation was specifically
// chosen to allow the end user to define a policy and validate the
// request appropriately before calling this function.
func ParseRequest(req *CertificateRequest) (csr, key []byte, err error) {
log.Info("received CSR")
if req.KeyRequest == nil {
req.KeyRequest = &KeyRequest{
Algo: DefaultKeyRequest.Algo,
Size: DefaultKeyRequest.Size,
}
}
log.Infof("generating key: %s-%d", req.KeyRequest.Algo, req.KeyRequest.Size)
priv, err := req.KeyRequest.Generate()
if err != nil {
err = cferr.Wrap(cferr.PrivateKeyError, cferr.GenerationFailed, err)
return
}
switch priv := priv.(type) {
case *rsa.PrivateKey:
key = x509.MarshalPKCS1PrivateKey(priv)
block := pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: key,
}
key = pem.EncodeToMemory(&block)
case *ecdsa.PrivateKey:
key, err = x509.MarshalECPrivateKey(priv)
if err != nil {
err = cferr.Wrap(cferr.PrivateKeyError, cferr.Unknown, err)
return
}
block := pem.Block{
Type: "EC PRIVATE KEY",
Bytes: key,
}
key = pem.EncodeToMemory(&block)
default:
panic("Generate should have failed to produce a valid key.")
}
var tpl = x509.CertificateRequest{
Subject: req.Name(),
SignatureAlgorithm: req.KeyRequest.SigAlgo(),
DNSNames: req.Hosts,
}
csr, err = x509.CreateCertificateRequest(rand.Reader, &tpl, priv)
if err != nil {
log.Errorf("failed to generate a CSR: %v", err)
// The use of CertificateError was a matter of some
// debate; it is the one edge case in which a new
// error category specifically for CSRs might be
// useful, but it was deemed that one edge case did
// not a new category justify.
err = cferr.Wrap(cferr.CertificateError, cferr.BadRequest, err)
return
}
block := pem.Block{
Type: "CERTIFICATE REQUEST",
Bytes: csr,
}
log.Info("encoded CSR")
csr = pem.EncodeToMemory(&block)
return
}
// A Generator is responsible for validating certificate requests.
type Generator struct {
Validator func(*CertificateRequest) error
}
// ProcessRequest validates and processes the incoming request. It is
// a wrapper around a validator and the ParseRequest function.
func (g *Generator) ProcessRequest(req *CertificateRequest) (csr, key []byte, err error) {
log.Info("generate received request")
err = g.Validator(req)
if err != nil {
log.Warningf("invalid request: %v", err)
return
}
csr, key, err = ParseRequest(req)
if err != nil {
return nil, nil, err
}
return
}

View File

@ -0,0 +1,373 @@
package csr
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"testing"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
)
// TestKeyRequest ensures that key generation returns the same type of
// key specified in the KeyRequest.
func TestKeyRequest(t *testing.T) {
var kr = &KeyRequest{"ecdsa", 256}
priv, err := kr.Generate()
if err != nil {
t.Fatalf("%v", err)
}
switch priv.(type) {
case *rsa.PrivateKey:
if kr.Algo != "rsa" {
t.Fatal("RSA key generated, but expected", kr.Algo)
}
case *ecdsa.PrivateKey:
if kr.Algo != "ecdsa" {
t.Fatal("ECDSA key generated, but expected", kr.Algo)
}
}
}
// TestPKIXName validates building a pkix.Name structure from a
// CertificateRequest.
func TestPKIXName(t *testing.T) {
var kr = KeyRequest{"ecdsa", 256}
var cr = &CertificateRequest{
CN: "Test Common Name",
Names: []Name{
{
C: "US",
ST: "California",
L: "San Francisco",
O: "CloudFlare, Inc.",
OU: "Systems Engineering",
},
{
C: "GB",
ST: "London",
L: "London",
O: "CloudFlare, Inc",
OU: "Systems Engineering",
},
},
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
KeyRequest: &kr,
}
name := cr.Name()
if len(name.Country) != 2 {
t.Fatal("Expected two countries in SubjInfo.")
} else if len(name.Province) != 2 {
t.Fatal("Expected two states in SubjInfo.")
} else if len(name.Locality) != 2 {
t.Fatal("Expected two localities in SubjInfo.")
} else if len(name.Country) != 2 {
t.Fatal("Expected two countries in SubjInfo.")
} else if len(name.Organization) != 2 {
t.Fatal("Expected two organization in SubjInfo.")
} else if len(name.OrganizationalUnit) != 2 {
t.Fatal("Expected two organizational units in SubjInfo.")
}
}
// TestParseRequest ensures that a valid certificate request does not
// error.
func TestParseRequest(t *testing.T) {
var kr = KeyRequest{"ecdsa", 256}
var cr = &CertificateRequest{
CN: "Test Common Name",
Names: []Name{
{
C: "US",
ST: "California",
L: "San Francisco",
O: "CloudFlare, Inc.",
OU: "Systems Engineering",
},
{
C: "GB",
ST: "London",
L: "London",
O: "CloudFlare, Inc",
OU: "Systems Engineering",
},
},
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
KeyRequest: &kr,
}
_, _, err := ParseRequest(cr)
if err != nil {
t.Fatalf("%v", err)
}
}
func whichCurve(sz int) elliptic.Curve {
switch sz {
case 256:
return elliptic.P256()
case 384:
return elliptic.P384()
case 521:
return elliptic.P521()
}
return nil
}
// TestECGeneration ensures that the proper curve is used depending on
// the bit size specified in a key request and that an appropriate
// signature algorithm is returned.
func TestECGeneration(t *testing.T) {
var eckey *ecdsa.PrivateKey
for _, sz := range []int{256, 384, 521} {
kr := &KeyRequest{Algo: "ecdsa", Size: sz}
priv, err := kr.Generate()
if err != nil {
t.Fatalf("%v", err)
}
eckey = priv.(*ecdsa.PrivateKey)
if eckey.Curve != whichCurve(sz) {
t.Fatal("Generated key has wrong curve.")
}
if sa := kr.SigAlgo(); sa == x509.UnknownSignatureAlgorithm {
t.Fatal("Invalid signature algorithm!")
}
}
}
func TestRSAKeyGeneration(t *testing.T) {
var rsakey *rsa.PrivateKey
for _, sz := range []int{2048, 3072, 4096} {
kr := &KeyRequest{Algo: "rsa", Size: sz}
priv, err := kr.Generate()
if err != nil {
t.Fatalf("%v", err)
}
rsakey = priv.(*rsa.PrivateKey)
if rsakey.PublicKey.N.BitLen() != kr.Size {
t.Fatal("Generated key has wrong size.")
}
if sa := kr.SigAlgo(); sa == x509.UnknownSignatureAlgorithm {
t.Fatal("Invalid signature algorithm!")
}
}
}
// TestBadKeyRequest ensures that generating a key from a KeyRequest
// fails with an invalid algorithm, or an invalid RSA or ECDSA key
// size. An invalid ECDSA key size is any size other than 256, 384, or
// 521; an invalid RSA key size is any size less than 2048 bits.
func TestBadKeyRequest(t *testing.T) {
kr := &KeyRequest{Algo: "yolocrypto", Size: 1024}
if _, err := kr.Generate(); err == nil {
t.Fatal("Key generation should fail with invalid algorithm")
} else if sa := kr.SigAlgo(); sa != x509.UnknownSignatureAlgorithm {
t.Fatal("The wrong signature algorithm was returned from SigAlgo!")
}
kr.Algo = "ecdsa"
if _, err := kr.Generate(); err == nil {
t.Fatal("Key generation should fail with invalid key size")
} else if sa := kr.SigAlgo(); sa != x509.ECDSAWithSHA1 {
t.Fatal("The wrong signature algorithm was returned from SigAlgo!")
}
kr.Algo = "rsa"
if _, err := kr.Generate(); err == nil {
t.Fatal("Key generation should fail with invalid key size")
} else if sa := kr.SigAlgo(); sa != x509.SHA1WithRSA {
t.Fatal("The wrong signature algorithm was returned from SigAlgo!")
}
}
// TestDefaultKeyRequest makes sure that certificate requests without
// explicit key requests fall back to the default key request.
func TestDefaultKeyRequest(t *testing.T) {
var req = &CertificateRequest{
Names: []Name{
{
C: "US",
ST: "California",
L: "San Francisco",
O: "CloudFlare",
OU: "Systems Engineering",
},
},
CN: "cloudflare.com",
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
}
_, priv, err := ParseRequest(req)
if err != nil {
t.Fatalf("%v", err)
}
// If the default key type changes, this will need to be changed.
block, _ := pem.Decode(priv)
if block == nil {
t.Fatal("Bad private key was generated!")
}
switch block.Type {
case "RSA PRIVATE KEY":
if DefaultKeyRequest.Algo != "rsa" {
t.Fatal("Invalid default key request.")
}
case "EC PRIVATE KEY":
if DefaultKeyRequest.Algo != "ecdsa" {
t.Fatal("Invalid default key request.")
}
}
}
// TestRSACertRequest validates parsing a certificate request with an
// RSA key.
func TestRSACertRequest(t *testing.T) {
var req = &CertificateRequest{
Names: []Name{
{
C: "US",
ST: "California",
L: "San Francisco",
O: "CloudFlare",
OU: "Systems Engineering",
},
},
CN: "cloudflare.com",
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
KeyRequest: &KeyRequest{
Algo: "rsa",
Size: 2048,
},
}
_, _, err := ParseRequest(req)
if err != nil {
t.Fatalf("%v", err)
}
}
// TestBadCertRequest checks for failure conditions of ParseRequest.
func TestBadCertRequest(t *testing.T) {
var req = &CertificateRequest{
Names: []Name{
{
C: "US",
ST: "California",
L: "San Francisco",
O: "CloudFlare",
OU: "Systems Engineering",
},
},
CN: "cloudflare.com",
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
KeyRequest: &KeyRequest{
Algo: "yolo-crypto",
Size: 2048,
},
}
_, _, err := ParseRequest(req)
if err == nil {
t.Fatal("ParseRequest should fail with a bad key algorithm.")
}
}
// testValidator is a stripped-down validator that checks to make sure
// the request has a common name. It should mimic some of the
// functionality expected in an actual validator.
func testValidator(req *CertificateRequest) error {
if req.CN == "" {
return errors.NewBadRequestMissingParameter("CN")
}
return nil
}
// TestGenerator ensures that a valid request is processed properly
// and returns a certificate request and key.
func TestGenerator(t *testing.T) {
g := &Generator{testValidator}
var req = &CertificateRequest{
Names: []Name{
{
C: "US",
ST: "California",
L: "San Francisco",
O: "CloudFlare",
OU: "Systems Engineering",
},
},
CN: "cloudflare.com",
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
KeyRequest: &KeyRequest{
Algo: "rsa",
Size: 2048,
},
}
_, _, err := g.ProcessRequest(req)
if err != nil {
t.Fatalf("%v", err)
}
}
// TestBadGenerator ensures that a request that fails the validator is
// not processed.
func TestBadGenerator(t *testing.T) {
g := &Generator{testValidator}
missingCN := &CertificateRequest{
Names: []Name{
{
C: "US",
ST: "California",
L: "San Francisco",
O: "CloudFlare",
OU: "Systems Engineering",
},
},
// Missing CN
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
KeyRequest: &KeyRequest{
Algo: "rsa",
Size: 2048,
},
}
_, _, err := g.ProcessRequest(missingCN)
if err == nil {
t.Fatalf("Request should have failed.")
}
}
func TestWeakCSR(t *testing.T) {
weakKey := &CertificateRequest{
Names: []Name{
{
C: "US",
ST: "California",
L: "San Francisco",
O: "CloudFlare",
OU: "Systems Engineering",
},
},
CN: "cloudflare.com",
Hosts: []string{"cloudflare.com", "www.cloudflare.com"},
KeyRequest: &KeyRequest{
Algo: "rsa",
Size: 1024,
},
}
g := &Generator{testValidator}
_, _, err := g.ProcessRequest(weakKey)
if err == nil {
t.Fatalf("Request should have failed.")
}
}

View File

@ -0,0 +1,45 @@
/*
Package errors provides error types returned in CF SSL.
1. Type Error is intended for errors produced by CF SSL packages.
It formats to a json object that consists of an error message and a 4-digit code for error reasoning.
Example: {"code":1002, "message": "Failed to decode certificate"}
The index of codes are listed below:
1XXX: CertificateError
1000: Unknown
1001: ReadFailed
1002: DecodeFailed
1003: ParseFailed
1100: SelfSigned
12XX: VerifyFailed
121X: CertificateInvalid
1210: NotAuthorizedToSign
1211: Expired
1212: CANotAuthorizedForThisName
1213: TooManyIntermediates
1214: IncompatibleUsage
1220: UnknownAuthority
2XXX: PrivatekeyError
2000: Unknown
2001: ReadFailed
2002: DecodeFailed
2003: ParseFailed
2100: Encrypted
2200: NotRSA
2300: KeyMismatch
2400: GenerationFailed
2500: Unavailable
3XXX: IntermediatesError
4XXX: RootError
5XXX: PolicyError
5100: NoKeyUsages
5200: InvalidPolicy
5300: InvalidRequest
6XXX: DialError
2. Type HttpError is intended for CF SSL API to consume. It contains a HTTP status code that will be read and returned
by the API server.
*/
package errors

View File

@ -0,0 +1,351 @@
package errors
import (
"crypto/x509"
"encoding/json"
"fmt"
)
// Error is the error type usually returned by functions in CF SSL package.
// It contains a 4-digit error code where the most significant digit
// describes the category where the error occurred and the rest 3 digits
// describe the specific error reason.
type Error struct {
ErrorCode int `json:"code"`
Message string `json:"message"`
}
// Category is the most significant digit of the error code.
type Category int
// Reason is the last 3 digits of the error code.
type Reason int
const (
// Success indicates no error occurred.
Success Category = 1000 * iota // 0XXX
// CertificateError indicates a fault in a certificate.
CertificateError // 1XXX
// PrivateKeyError indicates a fault in a private key.
PrivateKeyError // 2XXX
// IntermediatesError indicates a fault in an intermediate.
IntermediatesError // 3XXX
// RootError indicates a fault in a root.
RootError // 4XXX
// PolicyError indicates an error arising from a malformed or
// non-existent policy, or a breach of policy.
PolicyError // 5XXX
// DialError indicates a network fault.
DialError // 6XXX
// APIClientError indicates a problem with the API client.
APIClientError // 7XXX
// OCSPError indicates a problem with OCSP signing
OCSPError // 8XXX
)
// None is a non-specified error.
const (
None Reason = iota
)
// Warning code for a success
const (
BundleExpiringBit int = 1 << iota // 0x01
BundleNotUbiquitousBit // 0x02
)
// Parsing errors
const (
Unknown Reason = iota // X000
ReadFailed // X001
DecodeFailed // X002
ParseFailed // X003
)
// The following represent certificate non-parsing errors, and must be
// specified along with CertificateError.
const (
// SelfSigned indicates that a certificate is self-signed and
// cannot be used in the manner being attempted.
SelfSigned Reason = 100 * (iota + 1) // Code 11XX
// VerifyFailed is an X.509 verification failure. The least two
// significant digits of 12XX is determined as the actual x509
// error is examined.
VerifyFailed // Code 12XX
// BadRequest indicates that the certificate request is invalid.
BadRequest // Code 13XX
)
const (
certificateInvalid = 10 * (iota + 1) //121X
unknownAuthority //122x
)
// The following represent private-key non-parsing errors, and must be
// specified with PrivateKeyError.
const (
// Encrypted indicates that the private key is a PKCS #8 encrypted
// private key. At this time, CFSSL does not support decrypting
// these keys.
Encrypted Reason = 100 * (iota + 1) //21XX
// NotRSAOrECC indicates that they key is not an RSA or ECC
// private key; these are the only two private key types supported
// at this time by CFSSL.
NotRSAOrECC //22XX
// KeyMismatch indicates that the private key does not match
// the public key or certificate being presented with the key.
KeyMismatch //23XX
// GenerationFailed indicates that a private key could not
// be generated.
GenerationFailed //24XX
// Unavailable indicates that a private key mechanism (such as
// PKCS #11) was requested but support for that mechanism is
// not available.
Unavailable
)
// The following are policy-related non-parsing errors, and must be
// specified along with PolicyError.
const (
// NoKeyUsages indicates that the profile does not permit any
// key usages for the certificate.
NoKeyUsages Reason = 100 * (iota + 1) // 51XX
// InvalidPolicy indicates that policy being requested is not
// a valid policy or does not exist.
InvalidPolicy // 52XX
// InvalidRequest indicates a certificate request violated the
// constraints of the policy being applied to the request.
InvalidRequest // 53XX
)
// The following are API client related errors, and should be
// specified with APIClientError.
const (
// AuthenticationFailure occurs when the client is unable
// to obtain an authentication token for the request.
AuthenticationFailure Reason = 100 * (iota + 1)
// JSONError wraps an encoding/json error.
JSONError
// IOError wraps an io/ioutil error.
IOError
// ClientHTTPError wraps a net/http error.
ClientHTTPError
// ServerRequestFailed covers any other failures from the API
// client.
ServerRequestFailed
)
// The following are OCSP related errors, and should be
// specified with OCSPError
const (
// IssuerMismatch ocurs when the certificate in the OCSP signing
// request was not issued by the CA that this responder responds for.
IssuerMismatch Reason = 100 * (iota + 1) // 81XX
// InvalidStatus occurs when the OCSP signing requests includes an
// invalid value for the certificate status.
InvalidStatus
)
// The error interface implementation, which formats to a JSON object string.
func (e *Error) Error() string {
marshaled, err := json.Marshal(e)
if err != nil {
panic(err)
}
return string(marshaled)
}
// New returns an error that contains an error code and message derived from
// the given category, reason. Currently, to avoid confusion, it is not
// allowed to create an error of category Success
func New(category Category, reason Reason) *Error {
errorCode := int(category) + int(reason)
var msg string
switch category {
case OCSPError:
switch reason {
case ReadFailed:
msg = "No certificate provided"
case IssuerMismatch:
msg = "Certificate not issued by this issuer"
case InvalidStatus:
msg = "Invalid revocation status"
}
case CertificateError:
switch reason {
case Unknown:
msg = "Unknown certificate error"
case ReadFailed:
msg = "Failed to read certificate"
case DecodeFailed:
msg = "Failed to decode certificate"
case ParseFailed:
msg = "Failed to parse certificate"
case SelfSigned:
msg = "Certificate is self signed"
case VerifyFailed:
msg = "Unable to verify certificate"
case BadRequest:
msg = "Invalid certificate request"
default:
panic(fmt.Sprintf("Unsupported CF-SSL error reason %d under category CertificateError.",
reason))
}
case PrivateKeyError:
switch reason {
case Unknown:
msg = "Unknown private key error"
case ReadFailed:
msg = "Failed to read private key"
case DecodeFailed:
msg = "Failed to decode private key"
case ParseFailed:
msg = "Failed to parse private key"
case Encrypted:
msg = "Private key is encrypted."
case NotRSAOrECC:
msg = "Private key algorithm is not RSA or ECC"
case KeyMismatch:
msg = "Private key does not match public key"
case GenerationFailed:
msg = "Failed to new private key"
case Unavailable:
msg = "Private key is unavailable"
default:
panic(fmt.Sprintf("Unsupported CF-SSL error reason %d under category PrivateKeyError.",
reason))
}
case IntermediatesError:
switch reason {
case Unknown:
msg = "Unknown intermediate certificate error"
case ReadFailed:
msg = "Failed to read intermediate certificate"
case DecodeFailed:
msg = "Failed to decode intermediate certificate"
case ParseFailed:
msg = "Failed to parse intermediate certificate"
default:
panic(fmt.Sprintf("Unsupported CF-SSL error reason %d under category IntermediatesError.",
reason))
}
case RootError:
switch reason {
case Unknown:
msg = "Unknown root certificate error"
case ReadFailed:
msg = "Failed to read root certificate"
case DecodeFailed:
msg = "Failed to decode root certificate"
case ParseFailed:
msg = "Failed to parse root certificate"
default:
panic(fmt.Sprintf("Unsupported CF-SSL error reason %d under category RootError.",
reason))
}
case PolicyError:
switch reason {
case Unknown:
msg = "Unknown policy error"
case NoKeyUsages:
msg = "Invalid policy: no key usage available"
case InvalidPolicy:
msg = "Invalid or unknown policy"
case InvalidRequest:
msg = "Policy violation request"
default:
panic(fmt.Sprintf("Unsupported CF-SSL error reason %d under category PolicyError.",
reason))
}
case DialError:
switch reason {
case Unknown:
msg = "Failed to dial remote server"
default:
panic(fmt.Sprintf("Unsupported CF-SSL error reason %d under category DialError.",
reason))
}
case APIClientError:
switch reason {
case AuthenticationFailure:
msg = "API client authentication failure"
case JSONError:
msg = "API client JSON config error"
case ClientHTTPError:
msg = "API client HTTP error"
case IOError:
msg = "API client IO error"
case ServerRequestFailed:
msg = "API client error: Server request failed"
default:
panic(fmt.Sprintf("Unsupported CF-SSL error reason %d under category APIClientError.",
reason))
}
default:
panic(fmt.Sprintf("Unsupported CF-SSL error type: %d.",
category))
}
return &Error{ErrorCode: errorCode, Message: msg}
}
// Wrap returns an error that contains the given error and an error code derived from
// the given category, reason and the error. Currently, to avoid confusion, it is not
// allowed to create an error of category Success
func Wrap(category Category, reason Reason, err error) *Error {
errorCode := int(category) + int(reason)
if err == nil {
panic("Wrap needs a supplied error to initialize.")
}
// do not double wrap a error
switch err.(type) {
case *Error:
panic("Unable to wrap a wrapped error.")
}
switch category {
case CertificateError:
// given VerifyFailed , report the status with more detailed status code
// for some certificate errors we care.
if reason == VerifyFailed {
switch errorType := err.(type) {
case x509.CertificateInvalidError:
errorCode += certificateInvalid + int(errorType.Reason)
case x509.UnknownAuthorityError:
errorCode += unknownAuthority
}
}
case PrivateKeyError, IntermediatesError, RootError, PolicyError, DialError, APIClientError:
// no-op, just use the error
default:
panic(fmt.Sprintf("Unsupported CF-SSL error type: %d.",
category))
}
return &Error{ErrorCode: errorCode, Message: err.Error()}
}

View File

@ -0,0 +1,69 @@
package errors
import (
"crypto/x509"
"encoding/json"
"errors"
"testing"
)
func TestNew(t *testing.T) {
err := New(CertificateError, Unknown)
if err == nil {
t.Fatal("Error creation failed.")
}
if err.ErrorCode != int(CertificateError)+int(Unknown) {
t.Fatal("Error code construction failed.")
}
if err.Message != "Unknown certificate error" {
t.Fatal("Error message construction failed.")
}
}
func TestWrap(t *testing.T) {
msg := "Arbitrary error message"
err := Wrap(CertificateError, Unknown, errors.New(msg))
if err == nil {
t.Fatal("Error creation failed.")
}
if err.ErrorCode != int(CertificateError)+int(Unknown) {
t.Fatal("Error code construction failed.")
}
if err.Message != msg {
t.Fatal("Error message construction failed.")
}
err = Wrap(CertificateError, VerifyFailed, x509.CertificateInvalidError{Reason: x509.Expired})
if err == nil {
t.Fatal("Error creation failed.")
}
if err.ErrorCode != int(CertificateError)+int(VerifyFailed)+certificateInvalid+int(x509.Expired) {
t.Fatal("Error code construction failed.")
}
if err.Message != "x509: certificate has expired or is not yet valid" {
t.Fatal("Error message construction failed.")
}
}
func TestMarshal(t *testing.T) {
msg := "Arbitrary error message"
err := Wrap(CertificateError, Unknown, errors.New(msg))
bytes, _ := json.Marshal(err)
var received Error
json.Unmarshal(bytes, &received)
if received.ErrorCode != int(CertificateError)+int(Unknown) {
t.Fatal("Error code construction failed.")
}
if received.Message != msg {
t.Fatal("Error message construction failed.")
}
}
func TestErrorString(t *testing.T) {
msg := "Arbitrary error message"
err := Wrap(CertificateError, Unknown, errors.New(msg))
str := err.Error()
if str != `{"code":1000,"message":"`+msg+`"}` {
t.Fatal("Incorrect Error():", str)
}
}

View File

@ -0,0 +1,47 @@
package errors
import (
"errors"
"net/http"
)
// HTTPError is an augmented error with a HTTP status code.
type HTTPError struct {
StatusCode int
error
}
// Error implements the error interface.
func (e *HTTPError) Error() string {
return e.error.Error()
}
// NewMethodNotAllowed returns an appropriate error in the case that
// an HTTP client uses an invalid method (i.e. a GET in place of a POST)
// on an API endpoint.
func NewMethodNotAllowed(method string) *HTTPError {
return &HTTPError{http.StatusMethodNotAllowed, errors.New(`Method is not allowed:"` + method + `"`)}
}
// NewBadRequest creates a HttpError with the given error and error code 400.
func NewBadRequest(err error) *HTTPError {
return &HTTPError{http.StatusBadRequest, err}
}
// NewBadRequestString returns a HttpError with the supplied message
// and error code 400.
func NewBadRequestString(s string) *HTTPError {
return NewBadRequest(errors.New(s))
}
// NewBadRequestMissingParameter returns a 400 HttpError as a required
// parameter is missing in the HTTP request.
func NewBadRequestMissingParameter(s string) *HTTPError {
return NewBadRequestString(`Missing parameter "` + s + `"`)
}
// NewBadRequestUnwantedParameter returns a 400 HttpError as a unnecessary
// parameter is present in the HTTP request.
func NewBadRequestUnwantedParameter(s string) *HTTPError {
return NewBadRequestString(`Unwanted parameter "` + s + `"`)
}

View File

@ -0,0 +1,227 @@
// Package helpers implements utility functionality common to many
// CF-SSL packages.
package helpers
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"strings"
"time"
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/log"
)
// OneYear is a time.Duration representing a year's worth of seconds.
const OneYear = 8760 * time.Hour
// OneDay is a time.Duration representing a day's worth of seconds.
const OneDay = 24 * time.Hour
// KeyLength returns the bit size of ECDSA or RSA PublicKey
func KeyLength(key interface{}) int {
if key == nil {
return 0
}
if ecdsaKey, ok := key.(*ecdsa.PublicKey); ok {
return ecdsaKey.Curve.Params().BitSize
} else if rsaKey, ok := key.(*rsa.PublicKey); ok {
return rsaKey.N.BitLen()
}
return 0
}
// ExpiryTime returns the time when the certificate chain is expired.
func ExpiryTime(chain []*x509.Certificate) *time.Time {
if len(chain) == 0 {
return nil
}
notAfter := chain[0].NotAfter
for _, cert := range chain {
if cert.NotAfter.Before(notAfter) {
notAfter = cert.NotAfter
}
}
return &notAfter
}
// SignatureString returns the TLS signature string corresponding to
// an X509 signature algorithm.
func SignatureString(alg x509.SignatureAlgorithm) string {
switch alg {
case x509.MD2WithRSA:
return "MD2WithRSA"
case x509.MD5WithRSA:
return "MD5WithRSA"
case x509.SHA1WithRSA:
return "SHA1WithRSA"
case x509.SHA256WithRSA:
return "SHA256WithRSA"
case x509.SHA384WithRSA:
return "SHA384WithRSA"
case x509.SHA512WithRSA:
return "SHA512WithRSA"
case x509.DSAWithSHA1:
return "DSAWithSHA1"
case x509.DSAWithSHA256:
return "DSAWithSHA256"
case x509.ECDSAWithSHA1:
return "ECDSAWithSHA1"
case x509.ECDSAWithSHA256:
return "ECDSAWithSHA256"
case x509.ECDSAWithSHA384:
return "ECDSAWithSHA384"
case x509.ECDSAWithSHA512:
return "ECDSAWithSHA512"
default:
return "Unknown Signature"
}
}
// HashAlgoString returns the hash algorithm name contains in the signature
// method.
func HashAlgoString(alg x509.SignatureAlgorithm) string {
switch alg {
case x509.MD2WithRSA:
return "MD2"
case x509.MD5WithRSA:
return "MD5"
case x509.SHA1WithRSA:
return "SHA1"
case x509.SHA256WithRSA:
return "SHA256"
case x509.SHA384WithRSA:
return "SHA384"
case x509.SHA512WithRSA:
return "SHA512"
case x509.DSAWithSHA1:
return "SHA1"
case x509.DSAWithSHA256:
return "SHA256"
case x509.ECDSAWithSHA1:
return "SHA1"
case x509.ECDSAWithSHA256:
return "SHA256"
case x509.ECDSAWithSHA384:
return "SHA384"
case x509.ECDSAWithSHA512:
return "SHA512"
default:
return "Unknown Hash Algorithm"
}
}
// ParseCertificatesPEM parses a sequence of PEM-encoded certificate and returns them.
func ParseCertificatesPEM(certsPEM []byte) ([]*x509.Certificate, error) {
var certs []*x509.Certificate
var err error
certsPEM = bytes.TrimSpace(certsPEM)
for len(certsPEM) > 0 {
var cert *x509.Certificate
cert, certsPEM, err = ParseOneCertificateFromPEM(certsPEM)
if err != nil {
return nil, cferr.New(cferr.CertificateError, cferr.ParseFailed)
} else if cert == nil {
break
}
certs = append(certs, cert)
}
if len(certsPEM) > 0 {
return nil, cferr.New(cferr.CertificateError, cferr.DecodeFailed)
}
return certs, nil
}
// ParseSelfSignedCertificatePEM parses a PEM-encoded certificate and check if it is self-signed.
func ParseSelfSignedCertificatePEM(certPEM []byte) (*x509.Certificate, error) {
cert, err := ParseCertificatePEM(certPEM)
if err != nil {
return nil, err
}
if err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature); err != nil {
return nil, cferr.Wrap(cferr.CertificateError, cferr.VerifyFailed, err)
}
return cert, nil
}
// ParseCertificatePEM parses and returns a PEM-encoded certificate.
func ParseCertificatePEM(certPEM []byte) (*x509.Certificate, error) {
certPEM = bytes.TrimSpace(certPEM)
cert, rest, err := ParseOneCertificateFromPEM(certPEM)
if err != nil {
// Log the actual parsing error but throw a default parse error message.
log.Debugf("Certificate parsing error: %v", err)
return nil, cferr.New(cferr.CertificateError, cferr.ParseFailed)
} else if cert == nil {
return nil, cferr.New(cferr.CertificateError, cferr.DecodeFailed)
} else if len(rest) > 0 {
return nil, cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, errors.New("The PEM file should contain only one certificate."))
}
return cert, nil
}
// ParseOneCertificateFromPEM attempts to parse one certificate from the top of the certsPEM,
// which may contain multiple certs.
func ParseOneCertificateFromPEM(certsPEM []byte) (cert *x509.Certificate, rest []byte, err error) {
block, rest := pem.Decode(certsPEM)
if block == nil {
return nil, rest, nil
}
cert, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, rest, err
}
return
}
// ParsePrivateKeyPEM parses and returns a PEM-encoded private
// key. The private key may be either an unencrypted PKCS#8, PKCS#1,
// or elliptic private key.
func ParsePrivateKeyPEM(keyPEM []byte) (key crypto.Signer, err error) {
keyDER, _ := pem.Decode(keyPEM)
if keyDER == nil {
return nil, cferr.New(cferr.PrivateKeyError, cferr.DecodeFailed)
}
if procType, ok := keyDER.Headers["Proc-Type"]; ok {
if strings.Contains(procType, "ENCRYPTED") {
return nil, cferr.New(cferr.PrivateKeyError, cferr.Encrypted)
}
}
return ParsePrivateKeyDER(keyDER.Bytes)
}
// ParsePrivateKeyDER parses a PKCS #1, PKCS #8, or elliptic curve
// DER-encoded private key. The key must not be in PEM format.
func ParsePrivateKeyDER(keyDER []byte) (key crypto.Signer, err error) {
generalKey, err := x509.ParsePKCS8PrivateKey(keyDER)
if err != nil {
generalKey, err = x509.ParsePKCS1PrivateKey(keyDER)
if err != nil {
generalKey, err = x509.ParseECPrivateKey(keyDER)
if err != nil {
// We don't include the actual error into
// the final error. The reason might be
// we don't want to leak any info about
// the private key.
return nil, cferr.New(cferr.PrivateKeyError,
cferr.ParseFailed)
}
}
}
switch generalKey.(type) {
case *rsa.PrivateKey:
return generalKey.(*rsa.PrivateKey), nil
case *ecdsa.PrivateKey:
return generalKey.(*ecdsa.PrivateKey), nil
}
return nil, cferr.New(cferr.PrivateKeyError, cferr.ParseFailed)
}

View File

@ -0,0 +1,273 @@
package helpers
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"math"
"testing"
"time"
)
const (
testCertFile = "testdata/cert.pem"
testBundleFile = "testdata/bundle.pem"
testExtraWSCertFile = "testdata/cert_with_whitespace.pem"
testExtraWSBundleFile = "testdata/bundle_with_whitespace.pem"
testMessedUpBundleFile = "testdata/messed_up_bundle.pem"
testMessedUpCertFile = "testdata/messedupcert.pem"
testEmptyCertFile = "testdata/emptycert.pem"
testPrivateKey = "testdata/priv_key.pem"
testMessedUpPrivateKey = "testdata/messed_up_priv_key.pem"
testEncryptedPrivateKey = "testdata/enc_priv_key.pem"
testEmptyPem = "testdata/empty.pem"
testNoHeaderCert = "testdata/noheadercert.pem"
)
func TestKeyLength(t *testing.T) {
expNil := 0
recNil := KeyLength(nil)
if expNil != recNil {
t.Fatal("KeyLength on nil did not return 0")
}
expNonsense := 0
inNonsense := "string?"
outNonsense := KeyLength(inNonsense)
if expNonsense != outNonsense {
t.Fatal("KeyLength malfunctioning on nonsense input")
}
//test the ecdsa branch
ecdsaPriv, _ := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
ecdsaIn, _ := ecdsaPriv.Public().(*ecdsa.PublicKey)
expEcdsa := ecdsaIn.Curve.Params().BitSize
outEcdsa := KeyLength(ecdsaIn)
if expEcdsa != outEcdsa {
t.Fatal("KeyLength malfunctioning on ecdsa input")
}
//test the rsa branch
rsaPriv, _ := rsa.GenerateKey(rand.Reader, 256)
rsaIn, _ := rsaPriv.Public().(*rsa.PublicKey)
expRsa := rsaIn.N.BitLen()
outRsa := KeyLength(rsaIn)
if expRsa != outRsa {
t.Fatal("KeyLength malfunctioning on rsa input")
}
}
func TestExpiryTime(t *testing.T) {
// nil case
var expNil *time.Time
inNil := []*x509.Certificate{}
outNil := ExpiryTime(inNil)
if expNil != outNil {
t.Fatal("Expiry time is malfunctioning on empty input")
}
//read a pem file and use that expiry date
bytes, _ := ioutil.ReadFile(testBundleFile)
certs, err := ParseCertificatesPEM(bytes)
if err != nil {
t.Fatalf("%v", err)
}
expected := time.Date(2014, time.April, 15, 0, 0, 0, 0, time.UTC)
out := ExpiryTime(certs)
if out == nil {
t.Fatal("Expiry time returning null")
}
if *out != expected {
t.Fatalf("Expected %v, got %v", expected, *out)
}
}
func TestHashAlgoString(t *testing.T) {
if HashAlgoString(x509.MD2WithRSA) != "MD2" {
t.Fatal("standin")
}
if HashAlgoString(x509.MD5WithRSA) != "MD5" {
t.Fatal("standin")
}
if HashAlgoString(x509.SHA1WithRSA) != "SHA1" {
t.Fatal("standin")
}
if HashAlgoString(x509.SHA256WithRSA) != "SHA256" {
t.Fatal("standin")
}
if HashAlgoString(x509.SHA384WithRSA) != "SHA384" {
t.Fatal("standin")
}
if HashAlgoString(x509.SHA512WithRSA) != "SHA512" {
t.Fatal("standin")
}
if HashAlgoString(x509.DSAWithSHA1) != "SHA1" {
t.Fatal("standin")
}
if HashAlgoString(x509.DSAWithSHA256) != "SHA256" {
t.Fatal("standin")
}
if HashAlgoString(x509.ECDSAWithSHA1) != "SHA1" {
t.Fatal("standin")
}
if HashAlgoString(x509.ECDSAWithSHA256) != "SHA256" {
t.Fatal("standin")
}
if HashAlgoString(x509.ECDSAWithSHA384) != "SHA384" {
t.Fatal("standin")
}
if HashAlgoString(x509.ECDSAWithSHA512) != "SHA512" {
t.Fatal("standin")
}
if HashAlgoString(math.MaxInt32) != "Unknown Hash Algorithm" {
t.Fatal("standin")
}
}
func TestSignatureString(t *testing.T) {
if SignatureString(x509.MD2WithRSA) != "MD2WithRSA" {
t.Fatal("Signature String functioning improperly")
}
if SignatureString(x509.MD5WithRSA) != "MD5WithRSA" {
t.Fatal("Signature String functioning improperly")
}
if SignatureString(x509.SHA1WithRSA) != "SHA1WithRSA" {
t.Fatal("Signature String functioning improperly")
}
if SignatureString(x509.SHA256WithRSA) != "SHA256WithRSA" {
t.Fatal("Signature String functioning improperly")
}
if SignatureString(x509.SHA384WithRSA) != "SHA384WithRSA" {
t.Fatal("Signature String functioning improperly")
}
if SignatureString(x509.SHA512WithRSA) != "SHA512WithRSA" {
t.Fatal("Signature String functioning improperly")
}
if SignatureString(x509.DSAWithSHA1) != "DSAWithSHA1" {
t.Fatal("Signature String functioning improperly")
}
if SignatureString(x509.DSAWithSHA256) != "DSAWithSHA256" {
t.Fatal("Signature String functioning improperly")
}
if SignatureString(x509.ECDSAWithSHA1) != "ECDSAWithSHA1" {
t.Fatal("Signature String functioning improperly")
}
if SignatureString(x509.ECDSAWithSHA256) != "ECDSAWithSHA256" {
t.Fatal("Signature String functioning improperly")
}
if SignatureString(x509.ECDSAWithSHA384) != "ECDSAWithSHA384" {
t.Fatal("Signature String functioning improperly")
}
if SignatureString(x509.ECDSAWithSHA512) != "ECDSAWithSHA512" {
t.Fatal("Signature String functioning improperly")
}
if SignatureString(math.MaxInt32) != "Unknown Signature" {
t.Fatal("Signature String functioning improperly")
}
}
func TestParseCertificatePEM(t *testing.T) {
for _, testFile := range []string{testCertFile, testExtraWSCertFile} {
certPEM, err := ioutil.ReadFile(testFile)
if err != nil {
t.Fatal(err)
}
if _, err := ParseCertificatePEM(certPEM); err != nil {
t.Fatal(err)
}
}
for _, testFile := range []string{testBundleFile, testMessedUpCertFile, testEmptyCertFile} {
certPEM, err := ioutil.ReadFile(testFile)
if err != nil {
t.Fatal(err)
}
if _, err := ParseCertificatePEM(certPEM); err == nil {
t.Fatal("Incorrect cert failed to raise error")
}
}
}
func TestParseCertificatesPEM(t *testing.T) {
// expected cases
for _, testFile := range []string{testBundleFile, testExtraWSBundleFile} {
bundlePEM, err := ioutil.ReadFile(testFile)
if err != nil {
t.Fatal(err)
}
if _, err := ParseCertificatesPEM(bundlePEM); err != nil {
t.Fatal(err)
}
}
// test failure cases
// few lines deleted, then headers removed
for _, testFile := range []string{testMessedUpBundleFile, testNoHeaderCert} {
bundlePEM, err := ioutil.ReadFile(testFile)
if err != nil {
t.Fatal(err)
}
if _, err := ParseCertificatesPEM(bundlePEM); err == nil {
t.Fatal("Incorrectly-formatted file failed to produce an error")
}
}
}
func TestSelfSignedCertificatePEM(t *testing.T) {
testPEM, _ := ioutil.ReadFile(testCertFile)
_, err := ParseSelfSignedCertificatePEM(testPEM)
if err != nil {
t.Fatalf("%v", err)
}
// a few lines deleted from the pem file
wrongPEM, _ := ioutil.ReadFile(testMessedUpCertFile)
_, err2 := ParseSelfSignedCertificatePEM(wrongPEM)
if err2 == nil {
t.Fatal("Improper pem file failed to raise an error")
}
// alter the signature of a valid certificate
blk, _ := pem.Decode(testPEM)
blk.Bytes[len(blk.Bytes)-10]++ // some hacking to get to the sig
alteredBytes := pem.EncodeToMemory(blk)
_, err = ParseSelfSignedCertificatePEM(alteredBytes)
if err == nil {
t.Fatal("Incorrect cert failed to produce an error")
}
}
func TestParsePrivateKeyPEM(t *testing.T) {
// expected case
testPEM, _ := ioutil.ReadFile(testPrivateKey)
_, err := ParsePrivateKeyPEM(testPEM)
if err != nil {
t.Fatalf("%v", err)
}
// error cases
errCases := []string{
testMessedUpPrivateKey, // a few lines deleted
testEmptyPem, //empty file
testEncryptedPrivateKey, // encrypted key
}
for _, fname := range errCases {
testPEM, _ = ioutil.ReadFile(fname)
_, err = ParsePrivateKeyPEM(testPEM)
if err == nil {
t.Fatal("Incorrect private key failed to produce an error")
}
}
}

View File

@ -0,0 +1,53 @@
-----BEGIN CERTIFICATE-----
MIIEczCCAl2gAwIBAgIIDARj8BWNsscwCwYJKoZIhvcNAQELMIGMMQswCQYDVQQG
EwJVUzETMBEGA1UEChMKQ2xvdWRGbGFyZTEcMBoGA1UECxMTU3lzdGVtcyBFbmdp
bmVlcmluZzEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZv
cm5pYTEdMBsGA1UEAxMUY2xvdWRmbGFyZS1pbnRlci5jb20wHhcNMTQwMzAyMDAw
MDAwWhcNMTkwNDAxMDAwMDAwWjCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkNs
b3VkRmxhcmUxHDAaBgNVBAsTE1N5c3RlbXMgRW5naW5lZXJpbmcxFjAUBgNVBAcT
DVNhbiBGcmFuY2lzY28xEzARBgNVBAgTCkNhbGlmb3JuaWExHTAbBgNVBAMTFGNs
b3VkZmxhcmUtaW50ZXIuY29tMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEIVkjNJGw
f3F0XWJH7yQSVtxuoBidi5JNsQ7FhxEQcZEl3b+/1iF60TBY2Yi6KwJuA6nIE73P
IXGyfNhThw4D8CiZbackQ/ufgz2DyvxyWFDPzLr7TXeM/0wSp/imoxWeo4GIMIGF
MA4GA1UdDwEB/wQEAwIApDASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBRB
+YoiUjIm34/wBwHdJGE4Wufs/DAfBgNVHSMEGDAWgBTXXUgpaSwO9HOrQBxGqOOS
FHsHEDAfBgNVHREEGDAWghRjbG91ZGZsYXJlLWludGVyLmNvbTALBgkqhkiG9w0B
AQsDggIBACRqAC5EJEe+8ihv1WzCUMEMb7KtS0BqoNbdXE32ia66PgJSQmHcmeJd
FI1UjL0DlljTM2tc+8KxR/1/qnKiI+W/D4wFTWOY/JWFOd15q7lXuKGl+8PMkAHF
A145JCr6oZoO9G9wUwVUrbmXAbyPCOfzsEQ2+mD9F1ZpoEjzVhtGf0R+vnYrRw8j
4WCv5AIcYRAf7HZxbhMILF1bccNlqyUtdH+/MTHXpjkjJjA5KbsHBrAEfjAXkD7c
WWOay6m7mVWb3PPFmGorP6t29baEETK9ZTZSrfD9rnExjjUCftWJEn0M4Pp98DvT
br6+bg8jwtq73qdyOfNsC/Sod18UuHH7MTQA22yqAF5jIlcYtAHGlNnl+sDPZACs
369/Z9rOL9vPFL+Z3F/uJtqZzvN1QiCkj8jWzR0u9fh3eQwZADM2RwgwS4Gs2Ygh
PsypDo33sFOwfX93KqKBsTHssn8SSDDaSnZ8bu1ATEdshbVieecuQx40UadPuJpw
EPVqTR5AhviXQ9bKrTnU5T7EgkW9vNydkpLQQlMg3QE8hsndv4loGZbZGfNtqQHS
/mg1t07S+7OEa4YaMW+wVOBOqTdW7OXlZFLfCcF5SYLM0SnlTMklRMxiqI4JqZXH
0thnUGD0JjfLX4rTaZUzT3lrXXWzpS2jzutXQkjGv4nhGGprIDuT
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEkDCCA/ugAwIBAgIIWnP9jF/2nogwCwYJKoZIhvcNAQELMH0xCzAJBgNVBAYT
AlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2Nv
MRMwEQYDVQQKDApDbG91ZEZsYXJlMRQwEgYDVQQLDAtERVZfVEVTVElORzEWMBQG
A1UEAwwNQ0ZTU0xfVEVTVF9DQTAeFw0xNDAzMDEwMDAwMDBaFw0xNDA0MTUwMDAw
MDBaMIGMMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQ2xvdWRGbGFyZTEcMBoGA1UE
CxMTU3lzdGVtcyBFbmdpbmVlcmluZzEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzET
MBEGA1UECBMKQ2FsaWZvcm5pYTEdMBsGA1UEAxMUY2xvdWRmbGFyZS1pbnRlci5j
b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDlCnV+vj0sVPy8SqHL
AlI+xwnPhWgzj2VevD6Nz1Zu1BeQ5m5y4CWCf+GmRGTP7+a/C510Fw6rpmInB0Ng
xxwQ2rC08fJtCnijlGH/VjEPIHY5lRaAomcM8Rgx6JOuv9BpZJKpr9pyUMV53JeW
RbWuLH5nEMdyk9NpetS2gWxt4/D20QlhK/tHkROrcLmEUddwIGdwE8JzI88c77Fu
u6pgMtHKvl4GGH0yvb4T7PvCdH8V2tCH7bt8roXd9MSyFVy7uORkfouip7EsVREU
mlcY5EvpR141KXbZqiOQiusJ+u76mEUQNk8wCR1/CW/ii9v1BKOVjXwCfEtIXjg0
APJx1VNSSH6XoDpUETL+eQ4J0FL9XNbsDuYar7+zD0N1/5vSo3HLNRQR9f0lbsys
sWBEN+CxK19xyPumr21Z0bU0f1B5H52VSF0q3I1Ju9wRo994a7YipdGcmZ2lChmT
7r3mzlBTYl3poU26q34v8wG9U7Jv4fsZJ+RGebDI+TR3QG6Yod06l9oEYZxWXBY7
STOs8wuTu3huSnan/IpWnV017Vsc61D5G+QrqcxZdXckt3anZKCF75JpUnJ7vuow
TmmHlb8KIMa9mOvcuGX4P6mz8gTi2arl/aL27kj9Q0Jgv/y1ebe2Bx2P9TF6+VND
DL3J/vSVlFeqLt2reAIBKnytLwIDAQABo4GIMIGFMA4GA1UdDwEB/wQEAwIApDAS
BgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBTXXUgpaSwO9HOrQBxGqOOSFHsH
EDAfBgNVHSMEGDAWgBS4Xu+uZ1C31vMH5Wq+VbNnOg2SPjAfBgNVHREEGDAWghRj
bG91ZGZsYXJlLWludGVyLmNvbTALBgkqhkiG9w0BAQsDgYEAfPLKCAHnPzgMYLX/
fWznVvOEFAAYZByPFx4QdMBbDZUtxHyvJIBs6PdxrdSuDwSiMqE7qQIi+jzzwGl9
fC7vf45B2zCX0OW51QL2oWNBdKlGgB+b2pwyME82lX/Pr7V1GY10u+ep1xdZDnch
DaMsXjQQTJu0iuG3qKEuCmUwOmc=
-----END CERTIFICATE-----

View File

@ -0,0 +1,56 @@
-----BEGIN CERTIFICATE-----
MIIEczCCAl2gAwIBAgIIDARj8BWNsscwCwYJKoZIhvcNAQELMIGMMQswCQYDVQQG
EwJVUzETMBEGA1UEChMKQ2xvdWRGbGFyZTEcMBoGA1UECxMTU3lzdGVtcyBFbmdp
bmVlcmluZzEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZv
cm5pYTEdMBsGA1UEAxMUY2xvdWRmbGFyZS1pbnRlci5jb20wHhcNMTQwMzAyMDAw
MDAwWhcNMTkwNDAxMDAwMDAwWjCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkNs
b3VkRmxhcmUxHDAaBgNVBAsTE1N5c3RlbXMgRW5naW5lZXJpbmcxFjAUBgNVBAcT
DVNhbiBGcmFuY2lzY28xEzARBgNVBAgTCkNhbGlmb3JuaWExHTAbBgNVBAMTFGNs
b3VkZmxhcmUtaW50ZXIuY29tMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEIVkjNJGw
f3F0XWJH7yQSVtxuoBidi5JNsQ7FhxEQcZEl3b+/1iF60TBY2Yi6KwJuA6nIE73P
IXGyfNhThw4D8CiZbackQ/ufgz2DyvxyWFDPzLr7TXeM/0wSp/imoxWeo4GIMIGF
MA4GA1UdDwEB/wQEAwIApDASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBRB
+YoiUjIm34/wBwHdJGE4Wufs/DAfBgNVHSMEGDAWgBTXXUgpaSwO9HOrQBxGqOOS
FHsHEDAfBgNVHREEGDAWghRjbG91ZGZsYXJlLWludGVyLmNvbTALBgkqhkiG9w0B
AQsDggIBACRqAC5EJEe+8ihv1WzCUMEMb7KtS0BqoNbdXE32ia66PgJSQmHcmeJd
FI1UjL0DlljTM2tc+8KxR/1/qnKiI+W/D4wFTWOY/JWFOd15q7lXuKGl+8PMkAHF
A145JCr6oZoO9G9wUwVUrbmXAbyPCOfzsEQ2+mD9F1ZpoEjzVhtGf0R+vnYrRw8j
4WCv5AIcYRAf7HZxbhMILF1bccNlqyUtdH+/MTHXpjkjJjA5KbsHBrAEfjAXkD7c
WWOay6m7mVWb3PPFmGorP6t29baEETK9ZTZSrfD9rnExjjUCftWJEn0M4Pp98DvT
br6+bg8jwtq73qdyOfNsC/Sod18UuHH7MTQA22yqAF5jIlcYtAHGlNnl+sDPZACs
369/Z9rOL9vPFL+Z3F/uJtqZzvN1QiCkj8jWzR0u9fh3eQwZADM2RwgwS4Gs2Ygh
PsypDo33sFOwfX93KqKBsTHssn8SSDDaSnZ8bu1ATEdshbVieecuQx40UadPuJpw
EPVqTR5AhviXQ9bKrTnU5T7EgkW9vNydkpLQQlMg3QE8hsndv4loGZbZGfNtqQHS
/mg1t07S+7OEa4YaMW+wVOBOqTdW7OXlZFLfCcF5SYLM0SnlTMklRMxiqI4JqZXH
0thnUGD0JjfLX4rTaZUzT3lrXXWzpS2jzutXQkjGv4nhGGprIDuT
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEkDCCA/ugAwIBAgIIWnP9jF/2nogwCwYJKoZIhvcNAQELMH0xCzAJBgNVBAYT
AlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2Nv
MRMwEQYDVQQKDApDbG91ZEZsYXJlMRQwEgYDVQQLDAtERVZfVEVTVElORzEWMBQG
A1UEAwwNQ0ZTU0xfVEVTVF9DQTAeFw0xNDAzMDEwMDAwMDBaFw0xNDA0MTUwMDAw
MDBaMIGMMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQ2xvdWRGbGFyZTEcMBoGA1UE
CxMTU3lzdGVtcyBFbmdpbmVlcmluZzEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzET
MBEGA1UECBMKQ2FsaWZvcm5pYTEdMBsGA1UEAxMUY2xvdWRmbGFyZS1pbnRlci5j
b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDlCnV+vj0sVPy8SqHL
AlI+xwnPhWgzj2VevD6Nz1Zu1BeQ5m5y4CWCf+GmRGTP7+a/C510Fw6rpmInB0Ng
xxwQ2rC08fJtCnijlGH/VjEPIHY5lRaAomcM8Rgx6JOuv9BpZJKpr9pyUMV53JeW
RbWuLH5nEMdyk9NpetS2gWxt4/D20QlhK/tHkROrcLmEUddwIGdwE8JzI88c77Fu
u6pgMtHKvl4GGH0yvb4T7PvCdH8V2tCH7bt8roXd9MSyFVy7uORkfouip7EsVREU
mlcY5EvpR141KXbZqiOQiusJ+u76mEUQNk8wCR1/CW/ii9v1BKOVjXwCfEtIXjg0
APJx1VNSSH6XoDpUETL+eQ4J0FL9XNbsDuYar7+zD0N1/5vSo3HLNRQR9f0lbsys
sWBEN+CxK19xyPumr21Z0bU0f1B5H52VSF0q3I1Ju9wRo994a7YipdGcmZ2lChmT
7r3mzlBTYl3poU26q34v8wG9U7Jv4fsZJ+RGebDI+TR3QG6Yod06l9oEYZxWXBY7
STOs8wuTu3huSnan/IpWnV017Vsc61D5G+QrqcxZdXckt3anZKCF75JpUnJ7vuow
TmmHlb8KIMa9mOvcuGX4P6mz8gTi2arl/aL27kj9Q0Jgv/y1ebe2Bx2P9TF6+VND
DL3J/vSVlFeqLt2reAIBKnytLwIDAQABo4GIMIGFMA4GA1UdDwEB/wQEAwIApDAS
BgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBTXXUgpaSwO9HOrQBxGqOOSFHsH
EDAfBgNVHSMEGDAWgBS4Xu+uZ1C31vMH5Wq+VbNnOg2SPjAfBgNVHREEGDAWghRj
bG91ZGZsYXJlLWludGVyLmNvbTALBgkqhkiG9w0BAQsDgYEAfPLKCAHnPzgMYLX/
fWznVvOEFAAYZByPFx4QdMBbDZUtxHyvJIBs6PdxrdSuDwSiMqE7qQIi+jzzwGl9
fC7vf45B2zCX0OW51QL2oWNBdKlGgB+b2pwyME82lX/Pr7V1GY10u+ep1xdZDnch
DaMsXjQQTJu0iuG3qKEuCmUwOmc=
-----END CERTIFICATE-----

View File

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB7jCCAVmgAwIBAgIBADALBgkqhkiG9w0BAQUwJjEQMA4GA1UEChMHQWNtZSBD
bzESMBAGA1UEAxMJMTI3LjAuMC4xMB4XDTEyMDkwNzIyMDAwNFoXDTEzMDkwNzIy
MDUwNFowJjEQMA4GA1UEChMHQWNtZSBDbzESMBAGA1UEAxMJMTI3LjAuMC4xMIGd
MAsGCSqGSIb3DQEBAQOBjQAwgYkCgYEAm6f+jkP2t5q/vM0YAUZZkhq/EAYD+L1C
MS59jJOLomfDnKUWOGKi/k7URBg1HNL3vm7/ESDazZWFy9l/nibWxNkSUPkQIrvr
GsNivkRUzXkwgNX8IN8LOYAQ3BWxAqitXTpLjf4FeCTB6G59v9eYlAX3kicXRdY+
cqhEvLFbu3MCAwEAAaMyMDAwDgYDVR0PAQH/BAQDAgCgMA0GA1UdDgQGBAQBAgME
MA8GA1UdIwQIMAaABAECAwQwCwYJKoZIhvcNAQEFA4GBABndWRIcfi+QB9Sakr+m
dYnXTgYCnFio53L2Z+6EHTGG+rEhWtUEGhL4p4pzXX4siAnjWvwcgXTo92cafcfi
uB7wRfK+NL9CTJdpN6cdL+fiNHzH8hsl3bj1nL0CSmdn2hkUWVLbLhSgWlib/I8O
aq+K7aVrgHkPnWeRiG6tl+ZA
-----END CERTIFICATE-----

View File

@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIIB7jCCAVmgAwIBAgIBADALBgkqhkiG9w0BAQUwJjEQMA4GA1UEChMHQWNtZSBD
bzESMBAGA1UEAxMJMTI3LjAuMC4xMB4XDTEyMDkwNzIyMDAwNFoXDTEzMDkwNzIy
MDUwNFowJjEQMA4GA1UEChMHQWNtZSBDbzESMBAGA1UEAxMJMTI3LjAuMC4xMIGd
MAsGCSqGSIb3DQEBAQOBjQAwgYkCgYEAm6f+jkP2t5q/vM0YAUZZkhq/EAYD+L1C
MS59jJOLomfDnKUWOGKi/k7URBg1HNL3vm7/ESDazZWFy9l/nibWxNkSUPkQIrvr
GsNivkRUzXkwgNX8IN8LOYAQ3BWxAqitXTpLjf4FeCTB6G59v9eYlAX3kicXRdY+
cqhEvLFbu3MCAwEAAaMyMDAwDgYDVR0PAQH/BAQDAgCgMA0GA1UdDgQGBAQBAgME
MA8GA1UdIwQIMAaABAECAwQwCwYJKoZIhvcNAQEFA4GBABndWRIcfi+QB9Sakr+m
dYnXTgYCnFio53L2Z+6EHTGG+rEhWtUEGhL4p4pzXX4siAnjWvwcgXTo92cafcfi
uB7wRfK+NL9CTJdpN6cdL+fiNHzH8hsl3bj1nL0CSmdn2hkUWVLbLhSgWlib/I8O
aq+K7aVrgHkPnWeRiG6tl+ZA
-----END CERTIFICATE-----

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,2 @@
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----LSKFSKLF

View File

@ -0,0 +1,30 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,90B8A5792FA2FE75B2053582F3DF394F
yVY2xuth5fdJBg9gg+6eP3qTsr0CJ2mGEDW6rvYmYuATSRF1hVERrsznxJYjYLaw
JHec8FVr78y4aXxI/aFzlxLkS8f12WjTtIhzHwhzgSJDwVOXSRphnLAeHWnhEKLe
7kO+vzoTPIc3ECwdvtr6//z2tP1/sac+yIhL6C+x2rS5hFHhmDUXtILPxxfHJCiM
qtKiiOZz3W6008CeJMC9ZPlKHDvpq7aIL4rfVP/GkZ+/teQkgWNpMxac7+gWLKuK
109v6pu+8KT49D6SMsaZPvAb5PXcIB79ZCPI1JX0V26CKcswba4RHG/h1xifwyAF
OIvmK29mmFqbx5GPlUefRUuPwRJKCXFiK6LTdhCwLYodtXde4ibvOFYy4onGoVax
I5WVaOhQMqp+mxA6z7odrIvuFcQGixIA+peaaSbpNZSZGuxRvVefcdxPbJ+26Ijs
wq8uyalbwhKtjPTPNkMaaYzJdWS7wd2DS4RM9JT8Y1h6NTftCY3c+/txOlt5pQzW
T8n+NTd4o+PFOHzMnmEnrtf9Y/SSzXDB2OPCD95YdIXItQDdKcjK0NmnY8GNfkWL
G30NJNy3/DR7Sa5u4xuqNgcgTFhgZaOQ1IVB3p5VjknqAX3gWFu2DrqzbH45071A
He7VbdbzBpMHI2EdiCVOuK9fD/5sv25u9vVC2NHtG/YcoEQv+RB52TNHn9kdiMj1
gLaywPqGjFmaPxI0xX07BrL+D9RruUT1GAEyw4JAHuJZIyq3+V98wmV/pEqwc7hp
8WuSi6YddetfF4NPA5cGWt8qZ1it+wD/1ydQEAQsxdANqi0XVudYpYox02EoRS02
up0sd9zqz83pN9RyOOKtGcHdt85gb9DYRVeff1UszMaoVULxqxYetwtzpiHn6grL
DmnSk+DYgvXKOVt8tmSJysDTumhK1VN3xb34TYYJxeBOQJLzWFjGSELEpphZAQSj
rS4OM1FwoP48wvASGiWD4VUJ6v+6F+NDvJr01S+zWGLg1EeUZJmXGHW5GrGd4Kgx
3rdeOsrED9oXKp2cpgx9avXJ9upixja9MbAPp7RkSyeHMPvsuaI44xvOP3f0crmG
d/5CdBKVT7nFaeTGSx/78kHb3VJyopAMm9k0V3CheKwBXXSbXmV1+0muBxMHsEI3
aEKaI0y5cDfTewzo/U0l0kGtxF6kUPN1pdjFpAvssRlkGttFOC2nWxHwaNHpn7Kq
gFAlN6P4cyB6kb+LvckIYTZ/tV39dx7PfL0KG5TWjJ4a9GSoL1IrAhQq+Qv6oUEt
1vlejZoKyZ/35fni0fmeYNho+pCPimm6l+sHTuXkrWgGLr0S9O00HFLz11D7R4o9
7mF4JkMNztT+ENOdT4xQBi3OGjRGMwtE6PsQPfDeu13Vq6eDtdEGUdhW1kAsGnBi
eJRuysnGpnoWofJ7yS0+DhnS4GAVi907TMrQWwmez9V4CXl4NBc8X9T69TFL2LsW
2KU9NUXdiCRZqZHD41gd3+RuRA/oXh50V9oaow+uepwYKTFyzde5IH1/DgBd7tOd
Fa2fM5/zSA0uFPRb3yCVhRg5d6J9t5yaPAz7Jp0D1mDDGsMBD1O/FYJvWoANEwUX
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,48 @@
-----BEGIN CERTIFICATE-----
MIIEczCCAl2gAwIBAgIIDARj8BWNsscwCwYJKoZIhvcNAQELMIGMMQswCQYDVQQG
EwJVUzETMBEGA1UEChMKQ2xvdWRGbGFyZTEcMBoGA1UECxMTU3lzdGVtcyBFbmdp
bmVlcmluZzEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZv
cm5pYTEdMBsGA1UEAxMUY2xvdWRmbGFyZS1pbnRlci5jb20wHhcNMTQwMzAyMDAw
MDAwWhcNMTkwNDAxMDAwMDAwWjCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkNs
b3VkRmxhcmUxHDAaBgNVBAsTE1N5c3RlbXMgRW5naW5lZXJpbmcxFjAUBgNVBAcT
FHsHEDAfBgNVHREEGDAWghRjbG91ZGZsYXJlLWludGVyLmNvbTALBgkqhkiG9w0B
AQsDggIBACRqAC5EJEe+8ihv1WzCUMEMb7KtS0BqoNbdXE32ia66PgJSQmHcmeJd
FI1UjL0DlljTM2tc+8KxR/1/qnKiI+W/D4wFTWOY/JWFOd15q7lXuKGl+8PMkAHF
A145JCr6oZoO9G9wUwVUrbmXAbyPCOfzsEQ2+mD9F1ZpoEjzVhtGf0R+vnYrRw8j
4WCv5AIcYRAf7HZxbhMILF1bccNlqyUtdH+/MTHXpjkjJjA5KbsHBrAEfjAXkD7c
WWOay6m7mVWb3PPFmGorP6t29baEETK9ZTZSrfD9rnExjjUCftWJEn0M4Pp98DvT
br6+bg8jwtq73qdyOfNsC/Sod18UuHH7MTQA22yqAF5jIlcYtAHGlNnl+sDPZACs
369/Z9rOL9vPFL+Z3F/uJtqZzvN1QiCkj8jWzR0u9fh3eQwZADM2RwgwS4Gs2Ygh
PsypDo33sFOwfX93KqKBsTHssn8SSDDaSnZ8bu1ATEdshbVieecuQx40UadPuJpw
EPVqTR5AhviXQ9bKrTnU5T7EgkW9vNydkpLQQlMg3QE8hsndv4loGZbZGfNtqQHS
/mg1t07S+7OEa4YaMW+wVOBOqTdW7OXlZFLfCcF5SYLM0SnlTMklRMxiqI4JqZXH
0thnUGD0JjfLX4rTaZUzT3lrXXWzpS2jzutXQkjGv4nhGGprIDuT
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEkDCCA/ugAwIBAgIIWnP9jF/2nogwCwYJKoZIhvcNAQELMH0xCzAJBgNVBAYT
AlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2Nv
MRMwEQYDVQQKDApDbG91ZEZsYXJlMRQwEgYDVQQLDAtERVZfVEVTVElORzEWMBQG
A1UEAwwNQ0ZTU0xfVEVTVF9DQTAeFw0xNDAzMDEwMDAwMDBaFw0xNDA0MTUwMDAw
MDBaMIGMMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQ2xvdWRGbGFyZTEcMBoGA1UE
CxMTU3lzdGVtcyBFbmdpbmVlcmluZzEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzET
MBEGA1UECBMKQ2FsaWZvcm5pYTEdMBsGA1UEAxMUY2xvdWRmbGFyZS1pbnRlci5j
b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDlCnV+vj0sVPy8SqHL
AlI+xwnPhWgzj2VevD6Nz1Zu1BeQ5m5y4CWCf+GmRGTP7+a/C510Fw6rpmInB0Ng
xxwQ2rC08fJtCnijlGH/VjEPIHY5lRaAomcM8Rgx6JOuv9BpZJKpr9pyUMV53JeW
RbWuLH5nEMdyk9NpetS2gWxt4/D20QlhK/tHkROrcLmEUddwIGdwE8JzI88c77Fu
u6pgMtHKvl4GGH0yvb4T7PvCdH8V2tCH7bt8roXd9MSyFVy7uORkfouip7EsVREU
mlcY5EvpR141KXbZqiOQiusJ+u76mEUQNk8wCR1/CW/ii9v1BKOVjXwCfEtIXjg0
APJx1VNSSH6XoDpUETL+eQ4J0FL9XNbsDuYar7+zD0N1/5vSo3HLNRQR9f0lbsys
sWBEN+CxK19xyPumr21Z0bU0f1B5H52VSF0q3I1Ju9wRo994a7YipdGcmZ2lChmT
7r3mzlBTYl3poU26q34v8wG9U7Jv4fsZJ+RGebDI+TR3QG6Yod06l9oEYZxWXBY7
STOs8wuTu3huSnan/IpWnV017Vsc61D5G+QrqcxZdXckt3anZKCF75JpUnJ7vuow
TmmHlb8KIMa9mOvcuGX4P6mz8gTi2arl/aL27kj9Q0Jgv/y1ebe2Bx2P9TF6+VND
DL3J/vSVlFeqLt2reAIBKnytLwIDAQABo4GIMIGFMA4GA1UdDwEB/wQEAwIApDAS
BgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBTXXUgpaSwO9HOrQBxGqOOSFHsH
EDAfBgNVHSMEGDAWgBS4Xu+uZ1C31vMH5Wq+VbNnOg2SPjAfBgNVHREEGDAWghRj
bG91ZGZsYXJlLWludGVyLmNvbTALBgkqhkiG9w0BAQsDgYEAfPLKCAHnPzgMYLX/
fWznVvOEFAAYZByPFx4QdMBbDZUtxHyvJIBs6PdxrdSuDwSiMqE7qQIi+jzzwGl9
fC7vf45B2zCX0OW51QL2oWNBdKlGgB+b2pwyME82lX/Pr7V1GY10u+ep1xdZDnch
DaMsXjQQTJu0iuG3qKEuCmUwOmc=
-----END CERTIFICATE-----
SDFSDKjkfdlsdfj

View File

@ -0,0 +1,20 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAvGKyz9ZzIXI/BFrtqbVQMmKQkPZGndyfV3AzeSb2ulbS/s5k
yNJMH/jKZiSCvZiJNnW+JNlJrgLxORMmPStPz/N/0L0vCTotQKZUiaBttFgHgobQ
LFsbMnumt9It5W/uOwgWI9binuzvqyPXywLlYwOq3jkOmA22ymhflzRrl6a3jzcY
hT9evxHl0gV4bN7KZ5p4wK/UUuG1uMEQLw87lUwRRHeW3ZG52VL38+redka+f5pa
SGKyG5j0oe1NPLqAjckNgqvDdPMY2gicmCq0VSLzTNpHRsURTUSJvC/iv34vVfba
gIYgTvm8BvGbJSlZqP4kEVlOfd3vmB0ttUeoDwIDAQABAoIBAHZdpXCFlA1d1U6N
O2s4a01dNOyAcVpa9xtfelgTLU9jomtLj3PG/uHP1oxbQHKUVxKK5JAOnwbg/mQY
LhydDCbjHlovpFAt56UJXXCkBoocDYvr3P0huXL80oIJY6EXtR4ONKsMJ5Qn12c2
vC3ogey2rzO1sf/EDigbcIR3AWtk1Tx8ZDUooktOFypIsDQgjjxXiURGssAlMPSh
6GVgO4JRRG6oRxEna7yDe7izmh/hC5sxSYLsEikCgYEAsBHhb/Qef5obRCSrfFuQ
41P7MCtGrXVxKD3iCDGQCzVbEbYGpmZnGsXSaHljp2FtnamaGGEudYziozGKPHjs
pbTbsLIDbmNwxz1WcaZ1iyIjtOxcAEqDod8hY4hL6SaxypwTHn4Ydbw2NGzp11Eg
Di4SVL82utjycATdKFvBzdsCgYB/3M+GMrt0Sh87rKcQLdL709Kzjcfhvm4HjIbJ
GSXGPCZaYMKaXRTdNAjtRKxMawc9qcf0xSBEHL0GkB158TzusDQtjP1anTcYOnl6
GsO4bRivp314iNlP4r3S3bIXqBxCGH3HbrvpdPFAN//qjYmAki2lFQZywfvbQOE8
oFQHwQKBgHqJkTck2DGlXQIwA7jirLggISXjSPlsG4w4LuhY9ivyNKLUi4x5k1cE
bX7SrRtJErQ1WaDN4TFG25xnysi5h+aPinuySatd0XmA5+dE1YjTqqShMO+lUpzi
PrOQl6Eva/uw5BDAcUH4AaXTNRvvtXQptUil9qXyOh6fszikA9Mm
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIB7jCCAVmgAwIBAgIBADALBgkqhkiG9w0BAQUwJjEQMA4GA1UEChMHQWNtZSBD
bzESMBAGA1UEAxMJMTI3LjAuMC4xMB4XDTEyMDkwNzIyMDAwNFoXDTEzMDkwNzIy
MDUwNFowJjEQMA4GA1UEChMHQWNtZSBDbzESMBAGA1UEAxMJMTI3LjAuMC4xMIGd
MAsGCSqGSIb3DQEBAQOBjQAwgYkCgYEAm6f+jkP2t5q/vM0YAUZZkhq/EAYD+L1C
cqhEvLFbu3MCAwEAAaMyMDAwDgYDVR0PAQH/BAQDAgCgMA0GA1UdDgQGBAQBAgME
MA8GA1UdIwQIMAaABAECAwQwCwYJKoZIhvcNAQEFA4GBABndWRIcfi+QB9Sakr+m
dYnXTgYCnFio53L2Z+6EHTGG+rEhWtUEGhL4p4pzXX4siAnjWvwcgXTo92cafcfi
uB7wRfK+NL9CTJdpN6cdL+fiNHzH8hsl3bj1nL0CSmdn2hkUWVLbLhSgWlib/I8O
aq+K7aVrgHkPnWeRiG6tl+ZA
-----END CERTIFICATE-----

View File

@ -0,0 +1,9 @@
MIIB7jCCAVmgAwIBAgIBADALBgkqhkiG9w0BAQUwJjEQMA4GA1UEChMHQWNtZSBD
bzESMBAGA1UEAxMJMTI3LjAuMC4xMB4XDTEyMDkwNzIyMDAwNFoXDTEzMDkwNzIy
MDUwNFowJjEQMA4GA1UEChMHQWNtZSBDbzESMBAGA1UEAxMJMTI3LjAuMC4xMIGd
MAsGCSqGSIb3DQEBAQOBjQAwgYkCgYEAm6f+jkP2t5q/vM0YAUZZkhq/EAYD+L1C
cqhEvLFbu3MCAwEAAaMyMDAwDgYDVR0PAQH/BAQDAgCgMA0GA1UdDgQGBAQBAgME
MA8GA1UdIwQIMAaABAECAwQwCwYJKoZIhvcNAQEFA4GBABndWRIcfi+QB9Sakr+m
dYnXTgYCnFio53L2Z+6EHTGG+rEhWtUEGhL4p4pzXX4siAnjWvwcgXTo92cafcfi
uB7wRfK+NL9CTJdpN6cdL+fiNHzH8hsl3bj1nL0CSmdn2hkUWVLbLhSgWlib/I8O
aq+K7aVrgHkPnWeRiG6tl+ZA

View File

@ -0,0 +1,28 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAvGKyz9ZzIXI/BFrtqbVQMmKQkPZGndyfV3AzeSb2ulbS/s5k
yNJMH/jKZiSCvZiJNnW+JNlJrgLxORMmPStPz/N/0L0vCTotQKZUiaBttFgHgobQ
LFsbMnumt9It5W/uOwgWI9binuzvqyPXywLlYwOq3jkOmA22ymhflzRrl6a3jzcY
hT9evxHl0gV4bN7KZ5p4wK/UUuG1uMEQLw87lUwRRHeW3ZG52VL38+redka+f5pa
SGKyG5j0oe1NPLqAjckNgqvDdPMY2gicmCq0VSLzTNpHRsURTUSJvC/iv34vVfba
gIYgTvm8BvGbJSlZqP4kEVlOfd3vmB0ttUeoDwIDAQABAoIBAHZdpXCFlA1d1U6N
O2s4a01dNOyAcVpa9xtfelgTLU9jomtLj3PG/uHP1oxbQHKUVxKK5JAOnwbg/mQY
LhydDCbjHlovpFAt56UJXXCkBoocDYvr3P0huXL80oIJY6EXtR4ONKsMJ5Qn12c2
vC3ogey2rzO1sf/EDigbcIR3AWtk1Tx8ZDUooktOFypIsDQgjjxXiURGssAlMPSh
1BFz4StRUK4bESaja0GiHwbuxHa+XYEBlK5OqMo/fpWqpgHhV/42+7wdcBMJsMr8
rFBe6m+r6TTbLSGJNowyd05XrjoAI35qduckpJ3Voun90i4ynTudjdJ/vHpIqB74
qQLFW2ECgYEA+GSRVqobaKKakNUFGmK0I5T5Tikz5f137YXXER6aLtDQNiSrlXNi
0aphkC/EfRO3oNvamq5+55bmmgDVoNNPDfpajKz+LZyG8GC2EXlTKO0hZS64KRRl
C+bd+ZsYiUDImNVRbIHN82f+BQgsgXlTaWpBOrEqmoePO/J44O4eX3cCgYEAwieq
amY4UaY+MhWPJFRK1y9M3hM8+N9N/35CFewQUdFJosC6vVQ4t8jNkSOxVQdgbNwE
i/bTBgIwg82JJYbBUPuCVeTT3i6zxymf/FLumrI73URD81IN6FiH1skg0hSFrvs0
6GVgO4JRRG6oRxEna7yDe7izmh/hC5sxSYLsEikCgYEAsBHhb/Qef5obRCSrfFuQ
41P7MCtGrXVxKD3iCDGQCzVbEbYGpmZnGsXSaHljp2FtnamaGGEudYziozGKPHjs
pbTbsLIDbmNwxz1WcaZ1iyIjtOxcAEqDod8hY4hL6SaxypwTHn4Ydbw2NGzp11Eg
Di4SVL82utjycATdKFvBzdsCgYB/3M+GMrt0Sh87rKcQLdL709Kzjcfhvm4HjIbJ
GSXGPCZaYMKaXRTdNAjtRKxMawc9qcf0xSBEHL0GkB158TzusDQtjP1anTcYOnl6
GsO4bRivp314iNlP4r3S3bIXqBxCGH3HbrvpdPFAN//qjYmAki2lFQZywfvbQOE8
oFQHwQKBgHqJkTck2DGlXQIwA7jirLggISXjSPlsG4w4LuhY9ivyNKLUi4x5k1cE
bX7SrRtJErQ1WaDN4TFG25xnysi5h+aPinuySatd0XmA5+dE1YjTqqShMO+lUpzi
PrOQl6Eva/uw5BDAcUH4AaXTNRvvtXQptUil9qXyOh6fszikA9Mm
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,98 @@
// Package log implements a wrapper around the Go standard library's
// logging package. Clients should set the current log level; only
// messages below that level will actually be logged. For example, if
// Level is set to LevelWarning, only log messages at the Warning,
// Error, and Critical levels will be logged.
package log
import (
"fmt"
golog "log"
)
// The following constants represent logging levels in increasing levels of seriousness.
const (
LevelDebug = iota
LevelInfo
LevelWarning
LevelError
LevelCritical
)
var levelPrefix = [...]string{
LevelDebug: "[DEBUG] ",
LevelInfo: "[INFO] ",
LevelWarning: "[WARNING] ",
LevelError: "[ERROR] ",
LevelCritical: "[CRITICAL] ",
}
// Level stores the current logging level.
var Level = LevelDebug
func outputf(l int, format string, v []interface{}) {
if l >= Level {
golog.Printf(fmt.Sprint(levelPrefix[l], format), v...)
}
}
func output(l int, v []interface{}) {
if l >= Level {
golog.Print(levelPrefix[l], fmt.Sprint(v...))
}
}
// Criticalf logs a formatted message at the "critical" level. The
// arguments are handled in the same manner as fmt.Printf.
func Criticalf(format string, v ...interface{}) {
outputf(LevelCritical, format, v)
}
// Critical logs its arguments at the "critical" level.
func Critical(v ...interface{}) {
output(LevelCritical, v)
}
// Errorf logs a formatted message at the "error" level. The arguments
// are handled in the same manner as fmt.Printf.
func Errorf(format string, v ...interface{}) {
outputf(LevelError, format, v)
}
// Error logs its arguments at the "error" level.
func Error(v ...interface{}) {
output(LevelError, v)
}
// Warningf logs a formatted message at the "warning" level. The
// arguments are handled in the same manner as fmt.Printf.
func Warningf(format string, v ...interface{}) {
outputf(LevelWarning, format, v)
}
// Warning logs its arguments at the "warning" level.
func Warning(v ...interface{}) {
output(LevelWarning, v)
}
// Infof logs a formatted message at the "info" level. The arguments
// are handled in the same manner as fmt.Printf.
func Infof(format string, v ...interface{}) {
outputf(LevelInfo, format, v)
}
// Info logs its arguments at the "info" level.
func Info(v ...interface{}) {
output(LevelInfo, v)
}
// Debugf logs a formatted message at the "debug" level. The arguments
// are handled in the same manner as fmt.Printf.
func Debugf(format string, v ...interface{}) {
outputf(LevelDebug, format, v)
}
// Debug logs its arguments at the "debug" level.
func Debug(v ...interface{}) {
output(LevelDebug, v)
}

View File

@ -0,0 +1 @@
package log

View File

@ -0,0 +1,165 @@
// Package local implements certificate signature functionality for CF-SSL.
package local
import (
"crypto"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"errors"
"io/ioutil"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/log"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer"
)
// Signer contains a signer that uses the standard library to
// support both ECDSA and RSA CA keys.
type Signer struct {
ca *x509.Certificate
priv crypto.Signer
policy *config.Signing
sigAlgo x509.SignatureAlgorithm
}
// NewSigner creates a new Signer directly from a
// private key and certificate, with optional policy.
func NewSigner(priv crypto.Signer, cert *x509.Certificate, sigAlgo x509.SignatureAlgorithm, policy *config.Signing) (*Signer, error) {
if policy == nil {
policy = &config.Signing{
Profiles: map[string]*config.SigningProfile{},
Default: config.DefaultConfig()}
}
if !policy.Valid() {
return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
}
return &Signer{
ca: cert,
priv: priv,
sigAlgo: sigAlgo,
policy: policy,
}, nil
}
// NewSignerFromFile generates a new local signer from a caFile
// and a caKey file, both PEM encoded.
func NewSignerFromFile(caFile, caKeyFile string, policy *config.Signing) (*Signer, error) {
log.Debug("Loading CA: ", caFile)
ca, err := ioutil.ReadFile(caFile)
if err != nil {
return nil, err
}
log.Debug("Loading CA key: ", caKeyFile)
cakey, err := ioutil.ReadFile(caKeyFile)
if err != nil {
return nil, cferr.Wrap(cferr.CertificateError, cferr.ReadFailed, err)
}
parsedCa, err := helpers.ParseCertificatePEM(ca)
if err != nil {
return nil, err
}
priv, err := helpers.ParsePrivateKeyPEM(cakey)
if err != nil {
log.Debug("Malformed private key %v", err)
return nil, err
}
return NewSigner(priv, parsedCa, signer.DefaultSigAlgo(priv), policy)
}
func (s *Signer) sign(template *x509.Certificate, profile *config.SigningProfile) (cert []byte, err error) {
err = signer.FillTemplate(template, s.policy.Default, profile)
if err != nil {
return
}
serialNumber := template.SerialNumber
var initRoot bool
if s.ca == nil {
if !template.IsCA {
err = cferr.New(cferr.PolicyError, cferr.InvalidRequest)
return
}
template.DNSNames = nil
s.ca = template
initRoot = true
template.MaxPathLen = signer.MaxPathLen
} else if template.IsCA {
template.MaxPathLen = 1
template.DNSNames = nil
}
derBytes, err := x509.CreateCertificate(rand.Reader, template, s.ca, template.PublicKey, s.priv)
if err != nil {
return
}
if initRoot {
s.ca, err = x509.ParseCertificate(derBytes)
if err != nil {
err = cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, err)
return
}
}
cert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
log.Infof("signed certificate with serial number %s", serialNumber)
return
}
// Sign signs a new certificate based on the PEM-encoded client
// certificate or certificate request with the signing profile,
// specified by profileName.
func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
profile := s.policy.Profiles[req.Profile]
if profile == nil {
profile = s.policy.Default
}
block, _ := pem.Decode([]byte(req.Request))
if block == nil {
return nil, cferr.New(cferr.CertificateError, cferr.DecodeFailed)
}
if block.Type != "CERTIFICATE REQUEST" {
return nil, cferr.Wrap(cferr.CertificateError,
cferr.BadRequest, errors.New("not a certificate or csr"))
}
template, err := signer.ParseCertificateRequest(s, block.Bytes, req.Subject, req.Hosts)
if err != nil {
return nil, err
}
return s.sign(template, profile)
}
// SigAlgo returns the RSA signer's signature algorithm.
func (s *Signer) SigAlgo() x509.SignatureAlgorithm {
return s.sigAlgo
}
// Certificate returns the signer's certificate.
func (s *Signer) Certificate(label, profile string) (*x509.Certificate, error) {
if label != "" {
return nil, cferr.NewBadRequestString("label specified for local operation")
}
cert := *s.ca
return &cert, nil
}
// SetPolicy sets the signer's signature policy.
func (s *Signer) SetPolicy(policy *config.Signing) {
s.policy = policy
}
// Policy returns the signer's policy.
func (s *Signer) Policy() *config.Signing {
return s.policy
}

View File

@ -0,0 +1,555 @@
package local
import (
"crypto/x509"
"io/ioutil"
"reflect"
"sort"
"strings"
"testing"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/csr"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/log"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer"
)
const (
testCaFile = "testdata/ca.pem"
testCaKeyFile = "testdata/ca_key.pem"
testECDSACaFile = "testdata/ecdsa256_ca.pem"
testECDSACaKeyFile = "testdata/ecdsa256_ca_key.pem"
)
var expiry = 1 * time.Minute
// Start a signer with the testing RSA CA cert and key.
func newTestSigner(t *testing.T) (s *Signer) {
s, err := NewSignerFromFile(testCaFile, testCaKeyFile, nil)
if err != nil {
t.Fatal(err)
}
return
}
func TestNewSignerFromFilePolicy(t *testing.T) {
var CAConfig = &config.Config{
Signing: &config.Signing{
Profiles: map[string]*config.SigningProfile{
"signature": &config.SigningProfile{
Usage: []string{"digital signature"},
Expiry: expiry,
},
},
Default: &config.SigningProfile{
Usage: []string{"cert sign", "crl sign"},
ExpiryString: "43800h",
Expiry: expiry,
CA: true,
},
},
}
_, err := NewSignerFromFile(testCaFile, testCaKeyFile, CAConfig.Signing)
if err != nil {
t.Fatal(err)
}
}
func TestNewSignerFromFileInvalidPolicy(t *testing.T) {
var invalidConfig = &config.Config{
Signing: &config.Signing{
Profiles: map[string]*config.SigningProfile{
"invalid": &config.SigningProfile{
Usage: []string{"wiretapping"},
Expiry: expiry,
},
"empty": &config.SigningProfile{},
},
Default: &config.SigningProfile{
Usage: []string{"digital signature"},
Expiry: expiry,
},
},
}
_, err := NewSignerFromFile(testCaFile, testCaKeyFile, invalidConfig.Signing)
if err == nil {
t.Fatal(err)
}
if !strings.Contains(err.Error(), `"code":5200`) {
t.Fatal(err)
}
}
func TestNewSignerFromFileNoUsageInPolicy(t *testing.T) {
var invalidConfig = &config.Config{
Signing: &config.Signing{
Profiles: map[string]*config.SigningProfile{
"invalid": &config.SigningProfile{
Usage: []string{},
Expiry: expiry,
},
"empty": &config.SigningProfile{},
},
Default: &config.SigningProfile{
Usage: []string{"digital signature"},
Expiry: expiry,
},
},
}
_, err := NewSignerFromFile(testCaFile, testCaKeyFile, invalidConfig.Signing)
if err == nil {
t.Fatal("expect InvalidPolicy error")
}
if !strings.Contains(err.Error(), `"code":5200`) {
t.Fatal(err)
}
}
func TestNewSignerFromFileEdgeCases(t *testing.T) {
res, err := NewSignerFromFile("nil", "nil", nil)
if res != nil && err == nil {
t.Fatal("Incorrect inputs failed to produce correct results")
}
res, err = NewSignerFromFile(testCaFile, "nil", nil)
if res != nil && err == nil {
t.Fatal("Incorrect inputs failed to produce correct results")
}
res, err = NewSignerFromFile("../../helpers/testdata/messedupcert.pem", "local.go", nil)
if res != nil && err == nil {
t.Fatal("Incorrect inputs failed to produce correct results")
}
res, err = NewSignerFromFile("../../helpers/testdata/cert.pem", "../../helpers/testdata/messed_up_priv_key.pem", nil)
if res != nil && err == nil {
t.Fatal("Incorrect inputs failed to produce correct results")
}
}
// test the private method
func testSign(t *testing.T) {
signer, err := NewSignerFromFile("testdata/ca.pem", "testdata/ca_key.pem", nil)
if signer == nil || err != nil {
t.Fatal("Failed to produce signer")
}
pem, _ := ioutil.ReadFile("../../helpers/testdata/cert.pem")
cert, _ := helpers.ParseCertificatePEM(pem)
badcert := *cert
badcert.PublicKey = nil
profl := config.SigningProfile{Usage: []string{"Certificates", "Rule"}}
_, err = signer.sign(&badcert, &profl)
if err == nil {
t.Fatal("Improper input failed to raise an error")
}
// nil profile
_, err = signer.sign(cert, &profl)
if err == nil {
t.Fatal("Nil profile failed to raise an error")
}
// empty profile
_, err = signer.sign(cert, &config.SigningProfile{})
if err == nil {
t.Fatal("Empty profile failed to raise an error")
}
// empty expiry
prof := signer.policy.Default
prof.Expiry = 0
_, err = signer.sign(cert, prof)
if err != nil {
t.Fatal("nil expiry raised an error")
}
// non empty urls
prof = signer.policy.Default
prof.CRL = "stuff"
prof.OCSP = "stuff"
prof.IssuerURL = []string{"stuff"}
_, err = signer.sign(cert, prof)
if err != nil {
t.Fatal("non nil urls raised an error")
}
// nil ca
nilca := *signer
prof = signer.policy.Default
prof.CA = false
nilca.ca = nil
_, err = nilca.sign(cert, prof)
if err == nil {
t.Fatal("nil ca with isca false raised an error")
}
prof.CA = true
_, err = nilca.sign(cert, prof)
if err != nil {
t.Fatal("nil ca with CA true raised an error")
}
}
func TestSign(t *testing.T) {
testSign(t)
s, err := NewSignerFromFile("testdata/ca.pem", "testdata/ca_key.pem", nil)
if err != nil {
t.Fatal("Failed to produce signer")
}
// test the empty request
_, err = s.Sign(signer.SignRequest{})
if err == nil {
t.Fatalf("Empty request failed to produce an error")
}
// not a csr
certPem, err := ioutil.ReadFile("../../helpers/testdata/cert.pem")
if err != nil {
t.Fatal(err)
}
// csr with ip as hostname
pem, err := ioutil.ReadFile("testdata/ip.csr")
if err != nil {
t.Fatal(err)
}
// improper request
validReq := signer.SignRequest{Hosts: signer.SplitHosts(testHostName), Request: string(certPem)}
_, err = s.Sign(validReq)
if err == nil {
t.Fatal("A bad case failed to raise an error")
}
validReq = signer.SignRequest{Hosts: signer.SplitHosts("128.84.126.213"), Request: string(pem)}
_, err = s.Sign(validReq)
if err != nil {
t.Fatal("A bad case failed to raise an error")
}
pem, err = ioutil.ReadFile("testdata/ex.csr")
validReq = signer.SignRequest{
Request: string(pem),
Hosts: []string{"example.com"},
}
s.Sign(validReq)
if err != nil {
t.Fatal("Failed to sign")
}
}
func TestCertificate(t *testing.T) {
s, err := NewSignerFromFile("testdata/ca.pem", "testdata/ca_key.pem", nil)
if err != nil {
t.Fatal(err)
}
c, err := s.Certificate("", "")
if !reflect.DeepEqual(*c, *s.ca) || err != nil {
t.Fatal("Certificate() producing incorrect results")
}
}
func TestPolicy(t *testing.T) {
s, err := NewSignerFromFile("testdata/ca.pem", "testdata/ca_key.pem", nil)
if err != nil {
t.Fatal(err)
}
sgn := config.Signing{}
s.SetPolicy(&sgn)
if s.Policy() != &sgn {
t.Fatal("Policy is malfunctioning")
}
}
func newCustomSigner(t *testing.T, testCaFile, testCaKeyFile string) (s *Signer) {
s, err := NewSignerFromFile(testCaFile, testCaKeyFile, nil)
if err != nil {
t.Fatal(err)
}
return
}
func TestNewSignerFromFile(t *testing.T) {
newTestSigner(t)
}
const (
testHostName = "localhost"
)
func testSignFile(t *testing.T, certFile string) ([]byte, error) {
s := newTestSigner(t)
pem, err := ioutil.ReadFile(certFile)
if err != nil {
t.Fatal(err)
}
return s.Sign(signer.SignRequest{Hosts: signer.SplitHosts(testHostName), Request: string(pem)})
}
type csrTest struct {
file string
keyAlgo string
keyLen int
// Error checking function
errorCallback func(*testing.T, error)
}
// A helper function that returns a errorCallback function which expects an error.
func ExpectError() func(*testing.T, error) {
return func(t *testing.T, err error) {
if err == nil {
t.Fatal("Expected error. Got nothing.")
}
}
}
var csrTests = []csrTest{
{
file: "testdata/rsa2048.csr",
keyAlgo: "rsa",
keyLen: 2048,
errorCallback: nil,
},
{
file: "testdata/rsa3072.csr",
keyAlgo: "rsa",
keyLen: 3072,
errorCallback: nil,
},
{
file: "testdata/rsa4096.csr",
keyAlgo: "rsa",
keyLen: 4096,
errorCallback: nil,
},
{
file: "testdata/ecdsa256.csr",
keyAlgo: "ecdsa",
keyLen: 256,
errorCallback: nil,
},
{
file: "testdata/ecdsa384.csr",
keyAlgo: "ecdsa",
keyLen: 384,
errorCallback: nil,
},
{
file: "testdata/ecdsa521.csr",
keyAlgo: "ecdsa",
keyLen: 521,
errorCallback: nil,
},
}
func TestSignCSRs(t *testing.T) {
s := newTestSigner(t)
hostname := "cloudflare.com"
for _, test := range csrTests {
csr, err := ioutil.ReadFile(test.file)
if err != nil {
t.Fatal("CSR loading error:", err)
}
// It is possible to use different SHA2 algorithm with RSA CA key.
rsaSigAlgos := []x509.SignatureAlgorithm{x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA}
for _, sigAlgo := range rsaSigAlgos {
s.sigAlgo = sigAlgo
certBytes, err := s.Sign(signer.SignRequest{Hosts: signer.SplitHosts(hostname), Request: string(csr)})
if test.errorCallback != nil {
test.errorCallback(t, err)
} else {
if err != nil {
t.Fatalf("Expected no error. Got %s. Param %s %d", err.Error(), test.keyAlgo, test.keyLen)
}
cert, _ := helpers.ParseCertificatePEM(certBytes)
if cert.SignatureAlgorithm != s.SigAlgo() {
t.Fatal("Cert Signature Algorithm does not match the issuer.")
}
}
}
}
}
func TestECDSASigner(t *testing.T) {
s := newCustomSigner(t, testECDSACaFile, testECDSACaKeyFile)
hostname := "cloudflare.com"
for _, test := range csrTests {
csr, err := ioutil.ReadFile(test.file)
if err != nil {
t.Fatal("CSR loading error:", err)
}
// Try all ECDSA SignatureAlgorithm
SigAlgos := []x509.SignatureAlgorithm{x509.ECDSAWithSHA1, x509.ECDSAWithSHA256, x509.ECDSAWithSHA384, x509.ECDSAWithSHA512}
for _, sigAlgo := range SigAlgos {
s.sigAlgo = sigAlgo
certBytes, err := s.Sign(signer.SignRequest{Hosts: signer.SplitHosts(hostname), Request: string(csr)})
if test.errorCallback != nil {
test.errorCallback(t, err)
} else {
if err != nil {
t.Fatalf("Expected no error. Got %s. Param %s %d", err.Error(), test.keyAlgo, test.keyLen)
}
cert, _ := helpers.ParseCertificatePEM(certBytes)
if cert.SignatureAlgorithm != s.SigAlgo() {
t.Fatal("Cert Signature Algorithm does not match the issuer.")
}
}
}
}
}
const (
ecdsaInterCSR = "testdata/ecdsa256-inter.csr"
ecdsaInterKey = "testdata/ecdsa256-inter.key"
rsaInterCSR = "testdata/rsa2048-inter.csr"
rsaInterKey = "testdata/rsa2048-inter.key"
)
func TestCAIssuing(t *testing.T) {
var caCerts = []string{testCaFile, testECDSACaFile}
var caKeys = []string{testCaKeyFile, testECDSACaKeyFile}
var interCSRs = []string{ecdsaInterCSR, rsaInterCSR}
var interKeys = []string{ecdsaInterKey, rsaInterKey}
var CAPolicy = &config.Signing{
Default: &config.SigningProfile{
Usage: []string{"cert sign", "crl sign"},
ExpiryString: "1h",
Expiry: 1 * time.Hour,
CA: true,
},
}
var hostname = "cloudflare-inter.com"
// Each RSA or ECDSA root CA issues two intermediate CAs (one ECDSA and one RSA).
// For each intermediate CA, use it to issue additional RSA and ECDSA intermediate CSRs.
for i, caFile := range caCerts {
caKeyFile := caKeys[i]
s := newCustomSigner(t, caFile, caKeyFile)
s.policy = CAPolicy
for j, csr := range interCSRs {
csrBytes, _ := ioutil.ReadFile(csr)
certBytes, err := s.Sign(signer.SignRequest{Hosts: signer.SplitHosts(hostname), Request: string(csrBytes)})
if err != nil {
t.Fatal(err)
}
interCert, err := helpers.ParseCertificatePEM(certBytes)
if err != nil {
t.Fatal(err)
}
keyBytes, _ := ioutil.ReadFile(interKeys[j])
interKey, _ := helpers.ParsePrivateKeyPEM(keyBytes)
interSigner := &Signer{interCert, interKey, CAPolicy, signer.DefaultSigAlgo(interKey)}
for _, anotherCSR := range interCSRs {
anotherCSRBytes, _ := ioutil.ReadFile(anotherCSR)
bytes, err := interSigner.Sign(
signer.SignRequest{
Hosts: signer.SplitHosts(hostname),
Request: string(anotherCSRBytes),
})
if err != nil {
t.Fatal(err)
}
cert, err := helpers.ParseCertificatePEM(bytes)
if err != nil {
t.Fatal(err)
}
if cert.SignatureAlgorithm != interSigner.SigAlgo() {
t.Fatal("Cert Signature Algorithm does not match the issuer.")
}
}
}
}
}
const testCSR = "testdata/ecdsa256.csr"
func TestOverrideSubject(t *testing.T) {
csrPEM, err := ioutil.ReadFile(testCSR)
if err != nil {
t.Fatalf("%v", err)
}
req := &signer.Subject{
Names: []csr.Name{
{O: "example.net"},
},
}
s := newCustomSigner(t, testECDSACaFile, testECDSACaKeyFile)
request := signer.SignRequest{
Hosts: []string{"127.0.0.1", "localhost"},
Request: string(csrPEM),
Subject: req,
}
certPEM, err := s.Sign(request)
if err != nil {
t.Fatalf("%v", err)
}
cert, err := helpers.ParseCertificatePEM(certPEM)
if err != nil {
t.Fatalf("%v", err)
}
if cert.Subject.Organization[0] != "example.net" {
t.Fatalf("Failed to override subject: want example.net but have %s", cert.Subject.Organization[0])
}
log.Info("Overrode subject info")
}
func TestOverwriteHosts(t *testing.T) {
csrPEM, err := ioutil.ReadFile(testCSR)
if err != nil {
t.Fatalf("%v", err)
}
s := newCustomSigner(t, testECDSACaFile, testECDSACaKeyFile)
request := signer.SignRequest{
Hosts: []string{"127.0.0.1", "localhost"},
Request: string(csrPEM),
Subject: nil,
}
certPEM, err := s.Sign(request)
if err != nil {
t.Fatalf("%v", err)
}
cert, err := helpers.ParseCertificatePEM(certPEM)
if err != nil {
t.Fatalf("%v", err)
}
// get the hosts, and add the ips
hosts, ips := cert.DNSNames, cert.IPAddresses
for _, ip := range ips {
hosts = append(hosts, ip.String())
}
// compare the sorted host lists
sort.Strings(hosts)
sort.Strings(request.Hosts)
if !reflect.DeepEqual(hosts, request.Hosts) {
t.Fatalf("Hosts not the same. cert hosts: %v, expected: %v", hosts, request.Hosts)
}
log.Info("Overwrote hosts")
}

View File

@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEmzCCA4OgAwIBAgIMAMSvNBgypwaaSQ5iMA0GCSqGSIb3DQEBBQUAMIGMMQsw
CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy
YW5jaXNjbzETMBEGA1UEChMKQ0ZTU0wgVEVTVDEbMBkGA1UEAxMSQ0ZTU0wgVEVT
VCBSb290IENBMR4wHAYJKoZIhvcNAQkBFg90ZXN0QHRlc3QubG9jYWwwHhcNMTIx
MjEyMDIxMDMxWhcNMjIxMDIxMDIxMDMxWjCBjDELMAkGA1UEBhMCVVMxEzARBgNV
BAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzARBgNVBAoT
CkNGU1NMIFRFU1QxGzAZBgNVBAMTEkNGU1NMIFRFU1QgUm9vdCBDQTEeMBwGCSqG
SIb3DQEJARYPdGVzdEB0ZXN0LmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAsRp1xSfIDoD/40Bo4Hls3sFn4dav5NgxbZGpVyGF7dJI9u0eEnL4
BUGssPaUFLWC83CZxujUEiEfE0oKX+uOhhGv3+j5xSTNM764m2eSiN53cdZtK05d
hwq9uS8LtjKOQeN1mQ5qmiqxBMdjkKgMsVw5lMCgoYKo57kaKFyXzdpNVDzqw+pt
HWmuNtDQjK3qT5Ma06mYPmIGYhIZYLY7oJGg9ZEaNR0GIw4zIT5JRsNiaSb5wTLw
aa0n/4vLJyVjLJcYmJBvZWj8g+taK+C4INu/jGux+bmsC9hq14tbOaTNAn/NE0qN
8oHwcRBEqfOdEYdZkxI5NWPiKNW/Q+AeXQIDAQABo4H6MIH3MB0GA1UdDgQWBBS3
0veEuqg51fusEM4p/YuWpBPsvTCBxAYDVR0jBIG8MIG5gBS30veEuqg51fusEM4p
/YuWpBPsvaGBkqSBjzCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3Ju
aWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzARBgNVBAoTCkNGU1NMIFRFU1Qx
GzAZBgNVBAMTEkNGU1NMIFRFU1QgUm9vdCBDQTEeMBwGCSqGSIb3DQEJARYPdGVz
dEB0ZXN0LmxvY2FsggwAxK80GDKnBppJDmIwDwYDVR0TBAgwBgEB/wIBADANBgkq
hkiG9w0BAQUFAAOCAQEAJ7r1EZYDwed6rS0+YKHdkRGRQ5Rz6A9DIVBPXrSMAGj3
F5EF2m/GJbhpVbnNJTVlgP9DDyabOZNxzdrCr4cHMkYYnocDdgAodnkw6GZ/GJTc
depbVTR4TpihFNzeDEGJePrEwM1DouGswpu97jyuCYZ3z1a60+a+3C1GwWaJ7Aet
Uqm+yLTUrMISsfnDPqJdM1NeqW3jiZ4IgcqJkieCCSpag9Xuzrp9q6rjmePvlQkv
qz020JGg6VijJ+c6Tf5y0XqbAhkBTqYtVamu9gEth9utn12EhdNjTZMPKMjjgFUd
H0N6yOEuQMl4ky7RxZBM0iPyeob6i4z2LEQilgv9MQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxGnXFJ8gOgP/j
QGjgeWzewWfh1q/k2DFtkalXIYXt0kj27R4ScvgFQayw9pQUtYLzcJnG6NQSIR8T
Sgpf646GEa/f6PnFJM0zvribZ5KI3ndx1m0rTl2HCr25Lwu2Mo5B43WZDmqaKrEE
x2OQqAyxXDmUwKChgqjnuRooXJfN2k1UPOrD6m0daa420NCMrepPkxrTqZg+YgZi
EhlgtjugkaD1kRo1HQYjDjMhPklGw2JpJvnBMvBprSf/i8snJWMslxiYkG9laPyD
61or4Lgg27+Ma7H5uawL2GrXi1s5pM0Cf80TSo3ygfBxEESp850Rh1mTEjk1Y+Io
1b9D4B5dAgMBAAECggEAKHhjcSomDSptTwDo9mLI/h40HudwSlsc8GzYxZBjinUD
N2n39T9QbeMUE1xFenX/9qFEgq+xxnLLJx1EQacSapCgIAqdCO/f9HMgvGJumdg8
c0cMq1i9Bp7tu+OESZ5D48qWlOM2eQRIb08g8W11eRIaFmPuUPoKnuktkQuXpPJc
YbS/+JuA8SDwe6sV0cMCQuS+iHFfeGwWCKrDUkhLwcL3waW3od2XFyOeFFWFhl0h
HmM/mWKRuRdqR7hrmArTwFZVkB+o/1ywVYXIv+JQm0eNZ5PKLNJGL2f5oxbMR/JI
AoK0bAlJmYaFp96h1KpbPwLEL/0hHSWA7sAyJIgQAQKBgQDaEAZor/w4ZUTekT1+
cbId0yA+ikDXQOfXaNCSh9Pex+Psjd5zVVOqyVFJ29daRju3d7rmpN4Cm5V4h0l1
/2ad207rjCAnpCHtaddJWNyJzF2IL2IaoCZQRp0k7zOjBGQpoWDTwBaEin5CCv3P
kkdQkKz6FDP1xskHSLZr21/QCQKBgQDP6jXutEgGjf3yKpMFk/69EamJdon8clbt
hl7cOyWtobnZhdOWVZPe00Oo3Jag2aWgFFsm3EtwnUCnR4d4+fXRKS2LkhfIUZcz
cKy17Ileggdd8UGhL4RDrF/En9tJL86WcVkcoOrqLcGB2FLWrVhVpHFK74eLMCH/
uc/+ioPItQKBgHYoDsD08s7AGMQcoNx90MyWVLduhFnegoFW+wUa8jOZzieka6/E
wVQeR5yksZjpy3vLNYu6M83n7eLkM2rrm/fXGHlLcTTpm7SgEBZfPwivotKjEh5p
PrlqucWEk082lutz1RqHz+u7e1Rfzk2F7nx6GDBdeBYpw03eGXJx6QW5AoGBAIJq
4puyAEAET1fZNtHX7IGCk7sDXTi6LCbgE57HhzHr8V0t4fQ6CABMuvMwM1gATjEk
s6yjoLqqGUUUzDipanViBAy5fiuManC868lN7zkWDTLzQ3ytBqVAee4na/DziP27
ae9YTSLJwskE/alloLRP6zTbHUXE0n7LelmrX1DFAoGBAMFLl+Lu+WFgCHxBjn43
rHpJbQZQmsFhAMhkN4hsj6dJfAGn2gRLRiVRAika+8QF65xMZiVQWUVSUZADWERi
0SXGjzN1wYxO3Qzy3LYwws6fxFAq5lo79eb38yFT2lHdqK3x/QgiDSRVl+R6cExV
xQB518/lp2eIeMpglWByDwJX
-----END PRIVATE KEY-----

View File

@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBezCCASECAQAwgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMR0wGwYDVQQDExRjbG91ZGZsYXJl
LWludGVyLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLgOKlWwIAIeURde
yvDMhgfn6xPp1gn8oUeLmsniBm7I+j84IsVzUso8/MpjMZ9nB8lQUanhv3Kmqcyj
HNj+iFegMjAwBgkqhkiG9w0BCQ4xIzAhMB8GA1UdEQQYMBaCFGNsb3VkZmxhcmUt
aW50ZXIuY29tMAoGCCqGSM49BAMCA0gAMEUCIEJcy2mn2YyK8lVE+HHmr2OsmdbH
4CLDVXFBwxke8ObqAiEAx/il1cDKvQ/I36b4XjBnOX2jcQ5oaCNPFFBE74WQ/ps=
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEILbwI4u4bw+HtafMqFnrL7LOrqNEZH5rW5ygSrigfrVLoAoGCCqGSM49
AwEHoUQDQgAEuA4qVbAgAh5RF17K8MyGB+frE+nWCfyhR4uayeIGbsj6PzgixXNS
yjz8ymMxn2cHyVBRqeG/cqapzKMc2P6IVw==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBgTCCASgCAQAwgYYxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRcwFQYDVQQDEw5jbG91ZGZsYXJl
LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBn9Ldie6BOcMHezn2dPuYqW
z/NoLYMLGNBqhOxUyEidYClI0JW2pWyUgT3A2UazFp1WgE94y7Z+2YlfRz+vcrKg
PzA9BgkqhkiG9w0BCQ4xMDAuMCwGA1UdEQQlMCOCDmNsb3VkZmxhcmUuY29tghF3
d3djbG91ZGZsYXJlLmNvbTAKBggqhkjOPQQDAgNHADBEAiBM+QRxe8u6rkdr10Jy
cxbR6NxrGrNeg5QqiOqF96JEmgIgDbtjd5e3y3I8W/+ih2us3WtMxgnTXfqPd48i
VLcv28Q=
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDUzCCAj2gAwIBAgIIbjeSyheUvjYwCwYJKoZIhvcNAQELMIGMMQswCQYDVQQG
EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
bzETMBEGA1UEChMKQ0ZTU0wgVEVTVDEbMBkGA1UEAxMSQ0ZTU0wgVEVTVCBSb290
IENBMR4wHAYJKoZIhvcNAQkBFg90ZXN0QHRlc3QubG9jYWwwHhcNMTQwNTI0MDQ1
MTQwWhcNMTUwNTI0MDQ1NjQwWjCBizELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkNs
b3VkRmxhcmUxHDAaBgNVBAsTE1N5c3RlbXMgRW5naW5lZXJpbmcxFjAUBgNVBAcT
DVNhbiBGcmFuY2lzY28xEzARBgNVBAgTCkNhbGlmb3JuaWExHDAaBgNVBAMTE2Ns
b3VkZmxhcmUtbGVhZi5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASMRv3x
vcv4I5QF7we+23hES2waKDffBRhQMVVAOSIJcpb4JnzcVJiPJjNlMPbczi5vbzkQ
K2kkjOP+okqQia3go4GGMIGDMA4GA1UdDwEB/wQEAwIABDAdBgNVHSUEFjAUBggr
BgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQU
4t+cr91ma5IxOPeiezgN8W9FBNowHwYDVR0jBBgwFoAUt9L3hLqoOdX7rBDOKf2L
lqQT7L0wCwYJKoZIhvcNAQELA4IBAQAWloyDhrcYFSaZjzb8+UKxnukPUzd7BGaX
BvLktbN7hrX+z+ntA5UgXWo7uNgf2L3VwS0mVnRowwmrGV8Pbw9FX5WSisBQ+JJJ
JC4ABYT2N7N+B488zKZuMZY8NmSR/ples0Suz3oArUn4ZBGxANyOR6haBbYfupDF
LaCtAdQwZzNPfHAo2NsENSOlzGVhV0r1ZqalzkBf70K0KuAoLRbNG3Og17UeMb8K
5sXa7WvubgZ7/D3lr//F56yJYyfTq8SWcIi4e9AUWY5qK+Sr+7W9/gSY3baaHxY9
T9SO4O1ENFJ8ecWRPdsiBNCpl53qMuYW2lh72N35Iyug6qKFDYg5
-----END CERTIFICATE-----

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIC2qaVydr67HuwWMrPQ3ljCVSsnbV7HbN78KqEX6a0GuoAoGCCqGSM49
AwEHoUQDQgAEjEb98b3L+COUBe8Hvtt4REtsGig33wUYUDFVQDkiCXKW+CZ83FSY
jyYzZTD23M4ub285ECtpJIzj/qJKkImt4A==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBvzCCAUUCAQAwgYYxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRcwFQYDVQQDEw5jbG91ZGZsYXJl
LmNvbTB2MBAGByqGSM49AgEGBSuBBAAiA2IABBk/Q+zMsZOJGkufRzGCWtSUtRjq
0QqChDGWbHLaa0h6ODVeEoKYOMvFJTg4V186tuuBe97KEey0OPDegzCBp5kBIiwg
HB/0xWoKdnfdRk6VyjmubPx399cGoZn8aCqgC6A/MD0GCSqGSIb3DQEJDjEwMC4w
LAYDVR0RBCUwI4IOY2xvdWRmbGFyZS5jb22CEXd3d2Nsb3VkZmxhcmUuY29tMAoG
CCqGSM49BAMDA2gAMGUCMQC57VfwMXDyL5kM7vmO2ynbpgSAuFZT6Yd3C3NnV2jz
Biozw3eqIDXqCb2LI09stZMCMGIwCuVARr2IRctxf7AmX7/O2SIaIhCpMFKRedQ7
RiWGZIucp5r6AfT9381PB29bHA==
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICCjCCAWsCAQAwgYYxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRcwFQYDVQQDEw5jbG91ZGZsYXJl
LmNvbTCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAHt/s9KTZETzu94JIAjZ3BaS
toSG65hGIc1e0Gt7PhdQxPp5FP2D8rQ1wc+pcZhD2O8525kPxopaqTd+fWKBuD3O
AULzoH2OX+atIuumTQzLNbTsIbP0tY3dh7d8LItuERkZn1NfsNl3z6bnNAaR137m
f4aWv49ImbA/Tkv8VmoKX279oD8wPQYJKoZIhvcNAQkOMTAwLjAsBgNVHREEJTAj
gg5jbG91ZGZsYXJlLmNvbYIRd3d3Y2xvdWRmbGFyZS5jb20wCgYIKoZIzj0EAwQD
gYwAMIGIAkIA8OX9LxWOVnyfB25DFBz6JkjhyDpBM/PXlgLnWb/n2mEuMMB44DOG
pljDV768PSW11AC3DtULoIyR92z0TyLEKYoCQgHdGd6PwUtDW5mrAMJQDgebjsxu
MwfcdthzKlFlSmRpHMBnRMOJjlg5f9CTBg9d6wEdv7ZIrQSO6eqQHDQRM0VMnw==
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBnzCCAQgCAQAwXzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMQ8wDQYDVQQH
DAZJdGhhY2ExHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQxFDASBgNVBAMM
C2V4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBPmzv1c1e
QAa1yTtJ45oPOCARrhqDYV66urzNX1zHDZzi4lruIfI3q+1McACs4FIGJAkBUC2O
ZCamsR6ym5PaL9+dGfgVvf6w/GoBb65bxuw/IgHnzhfEHsk9nV8WthTEHmT9m9lh
kPMZBVDIVFW6iOCCpAwR6I9XXB30oKTINwIDAQABoAAwDQYJKoZIhvcNAQELBQAD
gYEAndd8OjJ+Jr74jqwuV9cUDqlItsLc84TYn+lly0EPezGQIIYz2KUoDyHQ+PQ9
7JI3G3FWR8Wpow7HooLJRxHNWOw7u8ekLCP0LjkoHse+Dou5C0jzo99jfrjXNWGt
DZO0Wrpu2eDclqwMJO/DtiovzcmOsGC52NHUW6+Moo9N2lM=
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBlTCB/wIBADBWMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxDzANBgNVBAcM
Bkl0aGFjYTEQMA4GA1UECgwHQ29ybmVsbDEXMBUGA1UEAwwOMTI4Ljg0LjEyNi4y
MTMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAME+bO/VzV5ABrXJO0njmg84
IBGuGoNhXrq6vM1fXMcNnOLiWu4h8jer7UxwAKzgUgYkCQFQLY5kJqaxHrKbk9ov
350Z+BW9/rD8agFvrlvG7D8iAefOF8QeyT2dXxa2FMQeZP2b2WGQ8xkFUMhUVbqI
4IKkDBHoj1dcHfSgpMg3AgMBAAGgADANBgkqhkiG9w0BAQsFAAOBgQBS7FBieNEN
PfXQRhPeiZ86QatshBBrj+TmhdC4GjtJ9lQA2NSRg2HnSHDErxdezZ7tw1ordd5D
hZpJ8XkPggsb7mghwPD7Zzgp0M/ldqbZ9fFEtNcpiEL05vKtap5uSGzNn32NDbQa
g+4QnDavffTQuzfuOoGJ9bG3jQtxo9HZCA==
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCbp/6OQ/a3mr+8zRgBRlmSGr8QBgP4vUIxLn2Mk4uiZ8OcpRY4
YqL+TtREGDUc0ve+bv8RINrNlYXL2X+eJtbE2RJQ+RAiu+saw2K+RFTNeTCA1fwg
3ws5gBDcFbECqK1dOkuN/gV4JMHobn2/15iUBfeSJxdF1j5yqES8sVu7cwIDAQAB
AoGBALZOnnBV3aLRlnw04kar9MCQnvLPeNteHyanQtjg/oxqZ8sR9+J2dFzSSv6u
M5bc6Nmb+xY+msZqt9g3l6bN6n+qCvNnLauIY/YPjd577uMTpx/QTOQSK8oc5Dhi
WgdU8GCtUmY+LE8qYx2NFitKCN4hubdrI76c+rnezIPVncZRAkEA9T5+vlfwk/Zl
DOte+JtbXx3RtXKFJPMirOFqNVp1qnIlUm8XtBW6760ugiNYbVbGHgbd8JsZnkPH
NC17TNLVJwJBAKJ7pDlJ2mvVr0cLrFhjAibz45dOipt8B4+dKtDIEuqbtKzJCGuP
SCk4X2SgYz0gC5kH62S7rn6Bsa9lM98dztUCQASdLWNFYkhWXWZV006YFar/c5+X
TPv5+xAHmajxT79qMFuRrX983Sx/NJ3MLnC4LjgIZwqM0HmSyt+nb2dtnAcCQCKi
nIUhuw+Vg0FvuZM1t7W581/DfERckfgJFqFepLmh60eRqtvStR0kSSFYFw9mj1JV
n9XfM/j/iHLM7du3rOkCQAw9R64yjcIBwcoSQxW/dr0Q9j+SnYgt+EhyXYXT30DS
DdOJ06GXtb/P0peFBp26BnQU4CSS75yseZ1TdB4ZqaA=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDCjCCAfQCAQAwgYYxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRcwFQYDVQQDEw5jbG91ZGZsYXJl
LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOLFLykOd2j31AQn
kaToYtstGvw5wLb4YnlzipQ6aULlD0H0GHM9IwhdSmcTWUWPb/U83g/ma1uD3Pp2
IdWd6xfjyOJF5XhgkyfRY65wS6vPZRm2MNSFXem+0AKHdhxIhb/QPMASqC/yaiPi
nvtOpBiCNl1Q2N4y9pkV0oD/T4rrn3RXP6iL1k4CNRS54JPCd+aI5Om+axVPU8Id
ZeUXQwXISaFrcC/bFXAHGX5hBMVu34lhCxvR4smweZkVmW++bIv26az8TSb5nVn4
TstLJIaOoOqot0sis04+0oX/GXfTPfkWyzfTVFN7cb9H+gz0FZJKtXQZv6qdntji
9FdR+pkCAwEAAaBAMD4GCSqGSIb3DQEJDjExMC8wLQYDVR0RBCYwJIIOY2xvdWRm
bGFyZS5jb22CEnd3dy5jbG91ZGZsYXJlLmNvbTALBgkqhkiG9w0BAQsDggEBABfM
9XTMqMqmfAAymWC4/W+vbh301KBoydcTnDQ/7B+ftHRE0O3FUsdL3wobj3qBieJo
MiQwiL7+GksszHvN9+YOUi70wpFuKghLhadb7p5GzL0+JgK2eQnLYb37/lQSiWwn
hht1YMOzErR/KHlxNUafk71bDEeytUcOvvtujf86nZiEnBpvp47zDjMkDersczM0
wj7S50IY8/vRsc2Q8vy+Q7D2FPEwjs4wCGVSqzwX2NPn3fZb/2pWRCie9kxHUfUP
L5xO4WoFGuirT6E2GnUWDdH661Pj5yEKvmr+qPl+eVoLjrtx0g5rAmA7rGlGrkqp
r4idH/BbJUaDlRHM/Hk=
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA4sUvKQ53aPfUBCeRpOhi2y0a/DnAtvhieXOKlDppQuUPQfQY
cz0jCF1KZxNZRY9v9TzeD+ZrW4Pc+nYh1Z3rF+PI4kXleGCTJ9FjrnBLq89lGbYw
1IVd6b7QAod2HEiFv9A8wBKoL/JqI+Ke+06kGII2XVDY3jL2mRXSgP9PiuufdFc/
qIvWTgI1FLngk8J35ojk6b5rFU9Twh1l5RdDBchJoWtwL9sVcAcZfmEExW7fiWEL
G9HiybB5mRWZb75si/bprPxNJvmdWfhOy0skho6g6qi3SyKzTj7Shf8Zd9M9+RbL
N9NUU3txv0f6DPQVkkq1dBm/qp2e2OL0V1H6mQIDAQABAoIBAQCzT3HcCAlZoeUu
p88dU3efkUnuOQhuZXcQS9E/JfTHpXHsF8Qhky0ZVxMW8BC91Q6VHt0EO5GWWm0o
SrK0Q9t6F25npRcumUaizIoCi9756tMpgouX8CDzTCMUbOJyuNGxe0oeImKFDyzo
VTCazHMqwgOUw/HHuQqOv9ekkrzlva8U+Z5MGZB4B2acHIAJHO9uYGzdeAjF3grm
dQ3QFGXJM0JzPmXfnUiDeOWIoVbo4YROFhf7qNlcnyLdkrYe0/XsSYQM9dRGKRPK
nkOkMv0sC8rOqNuJUn3tf1OOjzVQxlzB8Key6MOQ1c+kqsdCnL88/93CvI5NHazx
hwUmesmBAoGBAPpkDtgeWjxeIjOfuxXDYb04XbVmKquKNOIEk5OADmaacSGzdemh
XLRaNVMEYMcgMJViDDKW8g4k+zuZgzooMxNynlLNU5wfazwX2LLjReJFvZb/SxMM
N9+vQo8fcGz+p5g1tbeE6w86mpsTiAGx9Wa4J4GnY8jF6XUjZHO0X91pAoGBAOfZ
qrDkPMDSiVk62FP6LlPrj09bt1NTkBfv5dWhN/XeHjuus7unDhNiRmphhgF0VZse
XPtT/PUO0YgYlyaYJDDDE0IxgHuoK9wvEb2sqEtkZSw7IUhehheZ/+YfXzSA5fwa
vhXt0ghB0d9oVJuRoxb17MncjpjDAKy0QR5drR2xAoGBAMlNwkVseZ2JDLQ2WgHQ
N/cZpvUc83dAQO3pQgBW9rz0s7mlf0naqh5xW+enYGsW7RhcYHQXuPk4MCelbsRF
53JeNv1ZCDw/YkZI4bZIVDnrWdZY3zGsJAuY6skIPKnUPkd3/uVRXm267ut4U2MR
gLsZmOF7AxU6UEwVrT/8pwnpAoGAKxbVFlMUx3FZfW/mTJUujwI0fDc7dw0MtqYr
POzdjaBeVhE97h46C3g0Rgkh8ptAXbfi6ALP/GtonbaUQOP9teJLbf3tNw4mOKG2
1l2EWZ6q/vFuWhjXKwO//3DNLODX3WbK9SBh7I7vBmpJbzA980J5Y3rONa3oLjDB
+XbHecECgYEArOEv2D3fE3Hd6rEbxXinqekxMa+V1OCDO1IPz4wwr9RDMVUMxwqF
f0es1PQ2eMJGrAMbySxPfSZG05ou/tA+zR0qPwc/+dX0BbaXCiNT3gbhvL1L2fBc
7wr+MIUe2fi54JUWrUNMDHngRhXRKt2rZZRTfqVaFmZX02Y3fMZ2dWg=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDCTCCAfMCAQAwgYYxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRcwFQYDVQQDEw5jbG91ZGZsYXJl
LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALTWdoYxX4KN51fP
WxQAyGH++VsPbfpAoXIbCPXSmU04BvIxyjzpHQ0ChMKkT/2VNcUeFJwk2fCf+ZwU
f0raTQTplofwkckE0gEYA3WcEfJp+hbvbTb/2recsf+JE6JACYJe2Uu5wsjtrE5j
A+7aT2BEU9RWzBdSy/5281ZfW3PArqcWaf8+RUyA3WRxVWmjmhFsVB+mdNLhCpW0
C0QNMYR1ppEZiKVnEdao8gcI5sOvSd+35t8g82aPXcNSPU6jKcx1YNUPX5wgPEmu
+anfc9RliQbYqqJYVODgBmV8IR5grw93yTsODoWKtFQ4PKVlnt9CD8AS/iSMQYm3
OUogqgMCAwEAAaA/MD0GCSqGSIb3DQEJDjEwMC4wLAYDVR0RBCUwI4IOY2xvdWRm
bGFyZS5jb22CEXd3d2Nsb3VkZmxhcmUuY29tMAsGCSqGSIb3DQEBCwOCAQEAl809
gk9uZkRK+MJVYDSLjgGR2xqk5qOwnhovnispA7N3Z1GshodJRQa6ngNCKuXIm2/6
AxB9kDGK14n186Qq4odXqHSHs8FG9i0zUcBXeLv1rPAKtwKTas/SLmsOpPgWPZFa
iYiHHeu4HjOQoF987d7uGRYwc3xfstKwJsEXc12eCw2NH8TM1tJgSc/o6CzIpA91
QnZKhx6uGM4xI2gnOaJA1YikNhyFGBuOGMZgd0k2+/IcR2pg0z4pc5oQw1bXLANx
anqlA/MDrCM9v9019bRJ73zK8LQ3k/FW61PA9nL7RZ8ku65R+uYcVEdLa8pUeqnH
cJZNboDRsItpccZuRQ==
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIECTCCAnMCAQAwgYYxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRcwFQYDVQQDEw5jbG91ZGZsYXJl
LmNvbTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAL0zzgBv+VTwZOPy
LtuLFweQrj5Lfrje2hnNB7Y3TD4+yCM/cA4yTILixCe/B+N7LQysJgVDbW8u6BZQ
8ZqeDKOP6KCt37WhmcbT45tLpHmH+Z/uAnCz0hVc/7AyJ3CJXo6PaDCcJjgLuUun
W47iy4h79AxyuzELmUeZZGYcO8nqClqcnAzQ6sClGZvJwSbYg2QAFGoA2lHqZ9uN
ygAxNLd+rX9cP+yFwAeKzuKtOnVPiJD5lT3wufSkAbd6M7lOoqmTYnbv0A1WfA/e
upXno9lbgB6iwF5U0V7OtxdA1bTbvgJgNLlxFF1do0sB28CWmqCFNwLfzcPzt5A4
gLnOyLhNZOmUMXn35KOtp1Zv/yethlgZHxUYGcl6OYwMEFye3Du6dgnTwONzaLhA
7hMI8R60p2YrTLkgSKdFohAY/mKuxHyXxugOHHthlRCOn9m49edcdZ1HrkJXm9jd
P9katjCXgTwSdTQlvaMJkfH7wF3ZMjAxPcDf4RKFEpF2wABeNQIDAQABoD8wPQYJ
KoZIhvcNAQkOMTAwLjAsBgNVHREEJTAjgg5jbG91ZGZsYXJlLmNvbYIRd3d3Y2xv
dWRmbGFyZS5jb20wCwYJKoZIhvcNAQEMA4IBgQBF/RCHNAAOAaRI4VyO0tRPA5Dw
0/1/pgmBm/VejHIwDJnMFCl9njh0RSo1RgsVLhw6ovYbk3ORb4OD4UczPTq3GrFp
KP9uPR+2pR4FWJpCVfCl76YabQv6fUDdiT7ojzyRhsAmkd5rOdiMvWV3Rp+YmBuU
KH/dwkukfn+OeJIbERS5unzOBtQL+g5dU4CHWAqJQIqHr373w38OlYN+JY9QLrYy
sWU9Ye6RjdySXPJ5UzyfOEfc9Ji89RJsVeceB1+As5u5vBvtzGgIMSFUzN947RZo
DZ48JiB71VpmKXbn9LIRn25dlbVMzxRdSeZ194L3JFVAf9OxJTsc1QNFhOacoFgy
hqvtN2iKntEyPo2nacYhpz/FAdJ2JThNH+4WtpPWAqx8Lw/e1OttiDt+6M0FEuVz
svkSHnK206yo+a9Md37nUDDYxtlJEB+9F2qUZNQ7Hv+dxjmJOIgHOXxy1pLEdpVU
rGdGLVXeJNPCh9x+GK21QjdxZABmYAaF8k36Pv4=
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIFCTCCAvMCAQAwgYYxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRcwFQYDVQQDEw5jbG91ZGZsYXJl
LmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANkKL22jMn3eFCpj
T6lbeq4nC3aEqwTGrLARidAmO29WIhzs6LxRpM6xSMoPI6DvJVUGpMFEKF4xNTc5
X9/gSFrw2eI5Q3U3aGcaToSCxH4hXejwIzX8Ftlb/LfpXhbSsFr5MS3kiTY4zZxM
n3dSy2gZljD/g0tlQf5BdHdR4WKRhWnqRiGng+BmW4rjbcO7SoN33jSXsMcguCg5
8dmYuf5G5KVXsqwEoCQBeKGnca9orcm4i90VnGt4qZUpfAn1cADzYGpRzX79USJ6
tol4ovgGPN08LJFqcVl+dK8VzJ03JWBhI1jePbWS4Bz5oNtkhQQXilU+G6FQxc6a
UPf6KcFyOB+qMJmEwJZD9yaNK1YbsKfSztQEsb1JEezQnVHxp91Ch3AcWoikuOiY
yCg0V5lcK15SLv1+5sj9YzF7ngMmThcIJ6B5gS3swpD5AX6FJaI1BrGwT/RXKKQP
tRX1BySLx8RcINjFb5wv3q9QIE8vrW1BOk9f4dfmxiFYnc+6bCCbIrg7APQVtKTa
ixNJFSqZz7fm9loeNPHHXfUT5RoW5yzVa8igc+yv4qeYsWHcZ4c/Y91OJp19HMjM
bYm2alt8XagBgJjO0FW8wvsKwhhlhWK0WO6sQ7Fkl7fH1GtxEpc248hAW24SZMmS
led3LblCT8IC3a9BLhqJ2q8cfPp9AgMBAAGgPzA9BgkqhkiG9w0BCQ4xMDAuMCwG
A1UdEQQlMCOCDmNsb3VkZmxhcmUuY29tghF3d3djbG91ZGZsYXJlLmNvbTALBgkq
hkiG9w0BAQ0DggIBAAgz3NuN43+F+8+WhQ9hb7DOp6Amut7XubOkEBtBVgP3R8U1
uSsgocR1rvnZ1/bhkeGyTly0eQPhcSEdMo/GgIrcn+co0KLcDyV6Rf3Cgksx9dUZ
TzHSkxmFkxlxYfIGes6abH+2OPiacwK2gLvvmXFYIxEhv+LKzzteQi0xlinewv7R
FnSykZ4QialsFyCgOjOxa11aEdRv6T8qKwhjUOk0VedtzOkt/k95aydTNLjXl2OV
jloeTsbB00yWIqdyhG12+TgcJOa0pNP1zTjgFPodMuRUuiAcbT7Mt7sLCefKNzvZ
Ln6b4y7e6N3YLOHALTIP+LI4y8ar47WlXCNw/zeOM2sW8udjYrukN6WOV3X68oMf
Zsv6jqyGSaCDwdImR4VECUVvkabg9Sq4pz+ijTT+9cNA66omYL+/QAh0GahlROgW
kDGI8zeEUoAC8RkAbFGMJA8jEbAfbT000ZwnLX2SZ8YRQX4Jd1FTmAH99FkvvT8N
ovaGRSQQI5rWQGQYqF67So7PywEaEXeUHTBrv41Msva6CdaWHn7bh/fj4B21ETS7
VJvrk5DLJTyruqon7EVJU1pn38ppaXF4Z6a9n3C8TqudT/gdJUYn/SBo5jx20uGJ
d9k6vDqixntvk/TRZ848k1AXiv5uUJTdnoPPhzSGjxEaeKuB0R1ZHomVdjU4
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,4 @@
// Package pkcs11 implements support for PKCS #11 signers. If the
// package has not been built with the `pkcs11` tag, the `New`
// function will be a stub.
package pkcs11

View File

@ -0,0 +1,53 @@
// +build pkcs11
package pkcs11
import (
"io/ioutil"
"github.com/cloudflare/cfssl/crypto/pkcs11key"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/log"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/local"
)
// Config contains configuration information required to use a PKCS
// #11 key.
type Config struct {
Module string
Token string
PIN string
Label string
}
// Enabled is set to true if PKCS #11 support is present.
const Enabled = true
// New returns a new PKCS #11 signer.
func New(caCertFile string, policy *config.Signing, cfg *Config) (signer.Signer, error) {
if cfg == nil {
return nil, errors.New(errors.PrivateKeyError, errors.ReadFailed)
}
log.Debugf("Loading PKCS #11 module %s", cfg.Module)
certData, err := ioutil.ReadFile(caCertFile)
if err != nil {
return nil, errors.New(errors.PrivateKeyError, errors.ReadFailed)
}
cert, err := helpers.ParseCertificatePEM(certData)
if err != nil {
return nil, err
}
priv, err := pkcs11key.New(cfg.Module, cfg.Token, cfg.PIN, cfg.Label)
if err != nil {
return nil, errors.New(errors.PrivateKeyError, errors.ReadFailed)
}
sigAlgo := signer.DefaultSigAlgo(priv)
return local.NewSigner(priv, cert, sigAlgo, policy)
}

View File

@ -0,0 +1,27 @@
// +build !pkcs11
package pkcs11
import (
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer"
)
// Config contains configuration information required to use a PKCS
// #11 key.
type Config struct {
Module string
Token string
PIN string
Label string
}
// New always returns an error. If PKCS #11 support is needed, the
// program should be built with the `pkcs11` build tag.
func New(caCertFile string, policy *config.Signing, cfg *Config) (signer.Signer, error) {
return nil, errors.New(errors.PrivateKeyError, errors.Unknown)
}
// Enabled is set to true if PKCS #11 support is present.
const Enabled = false

View File

@ -0,0 +1,114 @@
package remote
import (
"crypto/x509"
"encoding/json"
"errors"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/api/client"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer"
)
// A Signer represents a CFSSL instance running as signing server.
// fulfills the signer.Signer interface
type Signer struct {
policy *config.Signing
}
// NewSigner creates a new remote Signer directly from a
// signing policy.
func NewSigner(policy *config.Signing) (*Signer, error) {
if policy != nil {
if !policy.Valid() {
return nil, cferr.New(cferr.PolicyError,
cferr.InvalidPolicy)
}
return &Signer{policy: policy}, nil
}
return nil, cferr.New(cferr.PolicyError,
cferr.InvalidPolicy)
}
// Sign sends a signature request to the remote CFSSL server,
// receiving a signed certificate or an error in response. The hostname,
// csr, and profileName are used as with a local signing operation, and
// the label is used to select a signing root in a multi-root CA.
func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
return s.remoteOp(req, req.Profile, "sign")
}
// Info sends an info request to the remote CFSSL server, receiving a signed
// certificate or an error in response.
func (s *Signer) Info(req client.InfoReq) (cert []byte, err error) {
return s.remoteOp(req, req.Profile, "info")
}
// Helper function to perform a remote sign or info request.
func (s *Signer) remoteOp(req interface{}, profile, target string) (cert []byte, err error) {
jsonData, err := json.Marshal(req)
if err != nil {
return nil, cferr.Wrap(cferr.APIClientError, cferr.JSONError, err)
}
var p *config.SigningProfile
if s.policy.Profiles != nil && profile != "" {
p = s.policy.Profiles[profile]
}
if p == nil {
p = s.policy.Default
}
server := client.NewServer(p.RemoteServer)
if server == nil {
return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidRequest,
errors.New("failed to connect to remote"))
}
// There's no server-side auth provider for the "info" method
// TODO: Revert this change once there is an AuthInfo provider.
if p.Provider != nil && target != "info" {
cert, err = server.AuthReq(jsonData, nil, p.Provider, target)
} else {
cert, err = server.Req(jsonData, target)
}
if err != nil {
return nil, err
}
return []byte(cert), nil
}
// SigAlgo returns the RSA signer's signature algorithm.
func (s *Signer) SigAlgo() x509.SignatureAlgorithm {
// TODO: implement this as a remote info call
return x509.UnknownSignatureAlgorithm
}
// Certificate returns the signer's certificate.
func (s *Signer) Certificate(label, profile string) (*x509.Certificate, error) {
certStr, err := s.Info(client.InfoReq{Label: label, Profile: profile})
if err != nil {
return nil, err
}
cert, err := helpers.ParseCertificatePEM(certStr)
if err != nil {
return nil, err
}
return cert, nil
}
// SetPolicy sets the signer's signature policy.
func (s *Signer) SetPolicy(policy *config.Signing) {
s.policy = policy
}
// Policy returns the signer's policy.
func (s *Signer) Policy() *config.Signing {
return s.policy
}

View File

@ -0,0 +1,399 @@
package remote
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/api"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/api/client"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/api/info"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/helpers"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/log"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/signer/local"
)
const (
testCaFile = "testdata/ca.pem"
testCaKeyFile = "testdata/ca_key.pem"
)
var validMinimalRemoteConfig = `
{
"signing": {
"default": {
"remote": "localhost"
}
},
"remotes": {
"localhost": "127.0.0.1:80"
}
}`
var validMinimalAuthRemoteConfig = `
{
"signing": {
"default": {
"auth_key": "sample",
"remote": "localhost"
}
},
"auth_keys": {
"sample": {
"type":"standard",
"key":"0123456789ABCDEF0123456789ABCDEF"
}
},
"remotes": {
"localhost": "127.0.0.1:80"
}
}`
func TestNewSigner(t *testing.T) {
remoteConfig := newConfig(t, []byte(validMinimalRemoteConfig))
_, err := NewSigner(remoteConfig.Signing)
if err != nil {
t.Fatal("fail to init remote signer:", err)
}
}
func TestNewAuthSigner(t *testing.T) {
remoteAuthConfig := newConfig(t, []byte(validMinimalAuthRemoteConfig))
_, err := NewSigner(remoteAuthConfig.Signing)
if err != nil {
t.Fatal("fail to init remote signer:", err)
}
}
func TestRemoteInfo(t *testing.T) {
remoteServer := newTestInfoServer(t)
defer closeTestServer(t, remoteServer)
remoteConfig := newConfig(t, []byte(validMinimalRemoteConfig))
// override with test server address, ignore url prefix "http://"
remoteConfig.Signing.OverrideRemotes(remoteServer.URL[7:])
s := newRemoteSigner(t, remoteConfig.Signing)
req := client.InfoReq{}
certBytes, err := s.Info(req)
if err != nil {
t.Fatal("remote info failed:", err)
}
caBytes, err := ioutil.ReadFile(testCaFile)
caBytes = bytes.TrimSpace(caBytes)
if err != nil {
t.Fatal("fail to read test CA cert:", err)
}
if bytes.Compare(caBytes, certBytes) != 0 {
t.Fatal("Get a different CA cert through info api.", len(certBytes), len(caBytes))
}
}
type csrTest struct {
file string
keyAlgo string
keyLen int
// Error checking function
errorCallback func(*testing.T, error)
}
var csrTests = []csrTest{
{
file: "../local/testdata/rsa2048.csr",
keyAlgo: "rsa",
keyLen: 2048,
errorCallback: nil,
},
{
file: "../local/testdata/rsa3072.csr",
keyAlgo: "rsa",
keyLen: 3072,
errorCallback: nil,
},
{
file: "../local/testdata/rsa4096.csr",
keyAlgo: "rsa",
keyLen: 4096,
errorCallback: nil,
},
{
file: "../local/testdata/ecdsa256.csr",
keyAlgo: "ecdsa",
keyLen: 256,
errorCallback: nil,
},
{
file: "../local/testdata/ecdsa384.csr",
keyAlgo: "ecdsa",
keyLen: 384,
errorCallback: nil,
},
{
file: "../local/testdata/ecdsa521.csr",
keyAlgo: "ecdsa",
keyLen: 521,
errorCallback: nil,
},
}
func TestRemoteSign(t *testing.T) {
remoteServer := newTestSignServer(t)
defer closeTestServer(t, remoteServer)
remoteConfig := newConfig(t, []byte(validMinimalRemoteConfig))
// override with test server address, ignore url prefix "http://"
remoteConfig.Signing.OverrideRemotes(remoteServer.URL[7:])
s := newRemoteSigner(t, remoteConfig.Signing)
hosts := []string{"cloudflare.com"}
for _, test := range csrTests {
csr, err := ioutil.ReadFile(test.file)
if err != nil {
t.Fatal("CSR loading error:", err)
}
certBytes, err := s.Sign(signer.SignRequest{Hosts: hosts, Request: string(csr)})
if test.errorCallback != nil {
test.errorCallback(t, err)
} else {
if err != nil {
t.Fatalf("Expected no error. Got %s. Param %s %d", err.Error(), test.keyAlgo, test.keyLen)
}
_, err := helpers.ParseCertificatePEM(certBytes)
if err != nil {
t.Fatal("Fail to parse returned certificate:", err)
}
}
}
}
func TestRemoteSignBadServerAndOverride(t *testing.T) {
remoteServer := newTestSignServer(t)
defer closeTestServer(t, remoteServer)
// remoteConfig contains port 80 that no test server will listen on
remoteConfig := newConfig(t, []byte(validMinimalRemoteConfig))
s := newRemoteSigner(t, remoteConfig.Signing)
hosts := []string{"cloudflare.com"}
csr, err := ioutil.ReadFile("../local/testdata/rsa2048.csr")
if err != nil {
t.Fatal("CSR loading error:", err)
}
_, err = s.Sign(signer.SignRequest{Hosts: hosts, Request: string(csr)})
if err == nil {
t.Fatal("Should return error")
}
remoteConfig.Signing.OverrideRemotes(remoteServer.URL[7:])
s.SetPolicy(remoteConfig.Signing)
certBytes, err := s.Sign(signer.SignRequest{Hosts: hosts, Request: string(csr)})
if err != nil {
t.Fatalf("Expected no error. Got %s.", err.Error())
}
_, err = helpers.ParseCertificatePEM(certBytes)
if err != nil {
t.Fatal("Fail to parse returned certificate:", err)
}
}
// helper functions
func newConfig(t *testing.T, configBytes []byte) *config.Config {
conf, err := config.LoadConfig([]byte(configBytes))
if err != nil {
t.Fatal("config loading error:", err)
}
if !conf.Valid() {
t.Fatal("config is not valid")
}
return conf
}
func newRemoteSigner(t *testing.T, policy *config.Signing) *Signer {
s, err := NewSigner(policy)
if err != nil {
t.Fatal("fail to init remote signer:", err)
}
return s
}
func newTestSignHandler(t *testing.T) (h http.Handler) {
h, err := newHandler(t, testCaFile, testCaKeyFile, "sign")
if err != nil {
t.Fatal(err)
}
return
}
func newTestInfoHandler(t *testing.T) (h http.Handler) {
h, err := newHandler(t, testCaFile, testCaKeyFile, "info")
if err != nil {
t.Fatal(err)
}
return
}
func newTestSignServer(t *testing.T) *httptest.Server {
mux := http.NewServeMux()
mux.Handle("/api/v1/cfssl/sign", newTestSignHandler(t))
ts := httptest.NewUnstartedServer(mux)
ts.Start()
t.Log(ts.URL)
return ts
}
func newTestInfoServer(t *testing.T) *httptest.Server {
mux := http.NewServeMux()
mux.Handle("/api/v1/cfssl/info", newTestInfoHandler(t))
ts := httptest.NewUnstartedServer(mux)
ts.Start()
t.Log(ts.URL)
return ts
}
func closeTestServer(t *testing.T, ts *httptest.Server) {
t.Log("Finalizing test server.")
ts.Close()
}
// newHandler generates a new sign handler (or info handler) using the certificate
// authority private key and certficate to sign certificates.
func newHandler(t *testing.T, caFile, caKeyFile, op string) (http.Handler, error) {
var expiry = 1 * time.Minute
var CAConfig = &config.Config{
Signing: &config.Signing{
Profiles: map[string]*config.SigningProfile{
"signature": &config.SigningProfile{
Usage: []string{"digital signature"},
Expiry: expiry,
},
},
Default: &config.SigningProfile{
Usage: []string{"cert sign", "crl sign"},
ExpiryString: "43800h",
Expiry: expiry,
CA: true,
},
},
}
s, err := local.NewSignerFromFile(testCaFile, testCaKeyFile, CAConfig.Signing)
if err != nil {
t.Fatal(err)
}
if op == "sign" {
return NewSignHandlerFromSigner(s)
} else if op == "info" {
return info.NewHandler(s)
}
t.Fatal("Bad op code")
return nil, nil
}
// NewSignHandlerFromSigner generates a new SignHandler directly from
// an existing signer.
func NewSignHandlerFromSigner(s signer.Signer) (h http.Handler, err error) {
policy := s.Policy()
if policy == nil {
err = errors.New(errors.PolicyError, errors.InvalidPolicy)
return
}
// Sign will only respond for profiles that have no auth provider.
// So if all of the profiles require authentication, we return an error.
haveUnauth := (policy.Default.Provider == nil)
for _, profile := range policy.Profiles {
if !haveUnauth {
break
}
haveUnauth = (profile.Provider == nil)
}
if !haveUnauth {
err = errors.New(errors.PolicyError, errors.InvalidPolicy)
return
}
return &api.HTTPHandler{
Handler: &SignHandler{
signer: s,
},
Method: "POST",
}, nil
}
// A SignHandler accepts requests with a hostname and certficate
// parameter (which should be PEM-encoded) and returns a new signed
// certificate. It includes upstream servers indexed by their
// profile name.
type SignHandler struct {
signer signer.Signer
}
// Handle responds to requests for the CA to sign the certificate request
// present in the "certificate_request" parameter for the host named
// in the "hostname" parameter. The certificate should be PEM-encoded. If
// provided, subject information from the "subject" parameter will be used
// in place of the subject information from the CSR.
func (h *SignHandler) Handle(w http.ResponseWriter, r *http.Request) error {
log.Info("signature request received")
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
r.Body.Close()
var req signer.SignRequest
err = json.Unmarshal(body, &req)
if err != nil {
return err
}
if len(req.Hosts) == 0 {
return errors.NewBadRequestString("missing paratmeter 'hosts'")
}
if req.Request == "" {
return errors.NewBadRequestString("missing parameter 'certificate_request'")
}
var cert []byte
var profile *config.SigningProfile
policy := h.signer.Policy()
if policy != nil && policy.Profiles != nil && req.Profile != "" {
profile = policy.Profiles[req.Profile]
}
if profile == nil && policy != nil {
profile = policy.Default
}
if profile.Provider != nil {
log.Error("profile requires authentication")
return errors.NewBadRequestString("authentication required")
}
cert, err = h.signer.Sign(req)
if err != nil {
log.Warningf("failed to sign request: %v", err)
return err
}
result := map[string]string{"certificate": string(cert)}
log.Info("wrote response")
return api.SendResponse(w, result)
}

View File

@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEmzCCA4OgAwIBAgIMAMSvNBgypwaaSQ5iMA0GCSqGSIb3DQEBBQUAMIGMMQsw
CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy
YW5jaXNjbzETMBEGA1UEChMKQ0ZTU0wgVEVTVDEbMBkGA1UEAxMSQ0ZTU0wgVEVT
VCBSb290IENBMR4wHAYJKoZIhvcNAQkBFg90ZXN0QHRlc3QubG9jYWwwHhcNMTIx
MjEyMDIxMDMxWhcNMjIxMDIxMDIxMDMxWjCBjDELMAkGA1UEBhMCVVMxEzARBgNV
BAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzARBgNVBAoT
CkNGU1NMIFRFU1QxGzAZBgNVBAMTEkNGU1NMIFRFU1QgUm9vdCBDQTEeMBwGCSqG
SIb3DQEJARYPdGVzdEB0ZXN0LmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAsRp1xSfIDoD/40Bo4Hls3sFn4dav5NgxbZGpVyGF7dJI9u0eEnL4
BUGssPaUFLWC83CZxujUEiEfE0oKX+uOhhGv3+j5xSTNM764m2eSiN53cdZtK05d
hwq9uS8LtjKOQeN1mQ5qmiqxBMdjkKgMsVw5lMCgoYKo57kaKFyXzdpNVDzqw+pt
HWmuNtDQjK3qT5Ma06mYPmIGYhIZYLY7oJGg9ZEaNR0GIw4zIT5JRsNiaSb5wTLw
aa0n/4vLJyVjLJcYmJBvZWj8g+taK+C4INu/jGux+bmsC9hq14tbOaTNAn/NE0qN
8oHwcRBEqfOdEYdZkxI5NWPiKNW/Q+AeXQIDAQABo4H6MIH3MB0GA1UdDgQWBBS3
0veEuqg51fusEM4p/YuWpBPsvTCBxAYDVR0jBIG8MIG5gBS30veEuqg51fusEM4p
/YuWpBPsvaGBkqSBjzCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3Ju
aWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzARBgNVBAoTCkNGU1NMIFRFU1Qx
GzAZBgNVBAMTEkNGU1NMIFRFU1QgUm9vdCBDQTEeMBwGCSqGSIb3DQEJARYPdGVz
dEB0ZXN0LmxvY2FsggwAxK80GDKnBppJDmIwDwYDVR0TBAgwBgEB/wIBADANBgkq
hkiG9w0BAQUFAAOCAQEAJ7r1EZYDwed6rS0+YKHdkRGRQ5Rz6A9DIVBPXrSMAGj3
F5EF2m/GJbhpVbnNJTVlgP9DDyabOZNxzdrCr4cHMkYYnocDdgAodnkw6GZ/GJTc
depbVTR4TpihFNzeDEGJePrEwM1DouGswpu97jyuCYZ3z1a60+a+3C1GwWaJ7Aet
Uqm+yLTUrMISsfnDPqJdM1NeqW3jiZ4IgcqJkieCCSpag9Xuzrp9q6rjmePvlQkv
qz020JGg6VijJ+c6Tf5y0XqbAhkBTqYtVamu9gEth9utn12EhdNjTZMPKMjjgFUd
H0N6yOEuQMl4ky7RxZBM0iPyeob6i4z2LEQilgv9MQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxGnXFJ8gOgP/j
QGjgeWzewWfh1q/k2DFtkalXIYXt0kj27R4ScvgFQayw9pQUtYLzcJnG6NQSIR8T
Sgpf646GEa/f6PnFJM0zvribZ5KI3ndx1m0rTl2HCr25Lwu2Mo5B43WZDmqaKrEE
x2OQqAyxXDmUwKChgqjnuRooXJfN2k1UPOrD6m0daa420NCMrepPkxrTqZg+YgZi
EhlgtjugkaD1kRo1HQYjDjMhPklGw2JpJvnBMvBprSf/i8snJWMslxiYkG9laPyD
61or4Lgg27+Ma7H5uawL2GrXi1s5pM0Cf80TSo3ygfBxEESp850Rh1mTEjk1Y+Io
1b9D4B5dAgMBAAECggEAKHhjcSomDSptTwDo9mLI/h40HudwSlsc8GzYxZBjinUD
N2n39T9QbeMUE1xFenX/9qFEgq+xxnLLJx1EQacSapCgIAqdCO/f9HMgvGJumdg8
c0cMq1i9Bp7tu+OESZ5D48qWlOM2eQRIb08g8W11eRIaFmPuUPoKnuktkQuXpPJc
YbS/+JuA8SDwe6sV0cMCQuS+iHFfeGwWCKrDUkhLwcL3waW3od2XFyOeFFWFhl0h
HmM/mWKRuRdqR7hrmArTwFZVkB+o/1ywVYXIv+JQm0eNZ5PKLNJGL2f5oxbMR/JI
AoK0bAlJmYaFp96h1KpbPwLEL/0hHSWA7sAyJIgQAQKBgQDaEAZor/w4ZUTekT1+
cbId0yA+ikDXQOfXaNCSh9Pex+Psjd5zVVOqyVFJ29daRju3d7rmpN4Cm5V4h0l1
/2ad207rjCAnpCHtaddJWNyJzF2IL2IaoCZQRp0k7zOjBGQpoWDTwBaEin5CCv3P
kkdQkKz6FDP1xskHSLZr21/QCQKBgQDP6jXutEgGjf3yKpMFk/69EamJdon8clbt
hl7cOyWtobnZhdOWVZPe00Oo3Jag2aWgFFsm3EtwnUCnR4d4+fXRKS2LkhfIUZcz
cKy17Ileggdd8UGhL4RDrF/En9tJL86WcVkcoOrqLcGB2FLWrVhVpHFK74eLMCH/
uc/+ioPItQKBgHYoDsD08s7AGMQcoNx90MyWVLduhFnegoFW+wUa8jOZzieka6/E
wVQeR5yksZjpy3vLNYu6M83n7eLkM2rrm/fXGHlLcTTpm7SgEBZfPwivotKjEh5p
PrlqucWEk082lutz1RqHz+u7e1Rfzk2F7nx6GDBdeBYpw03eGXJx6QW5AoGBAIJq
4puyAEAET1fZNtHX7IGCk7sDXTi6LCbgE57HhzHr8V0t4fQ6CABMuvMwM1gATjEk
s6yjoLqqGUUUzDipanViBAy5fiuManC868lN7zkWDTLzQ3ytBqVAee4na/DziP27
ae9YTSLJwskE/alloLRP6zTbHUXE0n7LelmrX1DFAoGBAMFLl+Lu+WFgCHxBjn43
rHpJbQZQmsFhAMhkN4hsj6dJfAGn2gRLRiVRAika+8QF65xMZiVQWUVSUZADWERi
0SXGjzN1wYxO3Qzy3LYwws6fxFAq5lo79eb38yFT2lHdqK3x/QgiDSRVl+R6cExV
xQB518/lp2eIeMpglWByDwJX
-----END PRIVATE KEY-----

View File

@ -0,0 +1,320 @@
// Package signer implements certificate signature functionality for CF-SSL.
package signer
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"math"
"math/big"
"net"
"strings"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/config"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/csr"
cferr "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cloudflare/cfssl/errors"
)
// MaxPathLen is the default path length for a new CA certificate.
var MaxPathLen = 2
// Subject contains the information that should be used to override the
// subject information when signing a certificate.
type Subject struct {
CN string
Names []csr.Name `json:"names"`
}
// SignRequest stores a signature request, which contains the hostname,
// the CSR, optional subject information, and the signature profile.
type SignRequest struct {
Hosts []string `json:"hosts"`
Request string `json:"certificate_request"`
Subject *Subject `json:"subject,omitempty"`
Profile string `json:"profile"`
Label string `json:"label"`
}
// appendIf appends to a if s is not an empty string.
func appendIf(s string, a *[]string) {
if s != "" {
*a = append(*a, s)
}
}
// Name returns the PKIX name for the subject.
func (s *Subject) Name() pkix.Name {
var name pkix.Name
name.CommonName = s.CN
for _, n := range s.Names {
appendIf(n.C, &name.Country)
appendIf(n.ST, &name.Province)
appendIf(n.L, &name.Locality)
appendIf(n.O, &name.Organization)
appendIf(n.OU, &name.OrganizationalUnit)
}
return name
}
// SplitHosts takes a comma-spearated list of hosts and returns a slice
// with the hosts split
func SplitHosts(hostList string) []string {
return strings.Split(hostList, ",")
}
// A Signer contains a CA's certificate and private key for signing
// certificates, a Signing policy to refer to and a SignatureAlgorithm.
type Signer interface {
Certificate(label, profile string) (*x509.Certificate, error)
Policy() *config.Signing
SetPolicy(*config.Signing)
SigAlgo() x509.SignatureAlgorithm
Sign(req SignRequest) (cert []byte, err error)
}
// DefaultSigAlgo returns an appropriate X.509 signature algorithm given
// the CA's private key.
func DefaultSigAlgo(priv crypto.Signer) x509.SignatureAlgorithm {
pub := priv.Public()
switch pub := pub.(type) {
case *rsa.PublicKey:
keySize := pub.N.BitLen()
switch {
case keySize >= 4096:
return x509.SHA512WithRSA
case keySize >= 3072:
return x509.SHA384WithRSA
case keySize >= 2048:
return x509.SHA256WithRSA
default:
return x509.SHA1WithRSA
}
case *ecdsa.PublicKey:
switch pub.Curve {
case elliptic.P256():
return x509.ECDSAWithSHA256
case elliptic.P384():
return x509.ECDSAWithSHA384
case elliptic.P521():
return x509.ECDSAWithSHA512
default:
return x509.ECDSAWithSHA1
}
default:
return x509.UnknownSignatureAlgorithm
}
}
// ParseCertificateRequest takes an incoming certificate request and
// builds a certificate template from it. If not nil, the subject
// information from subject will be used in place of the information in
// the CSR. The same is true for the list of hosts.
func ParseCertificateRequest(s Signer, csrBytes []byte, sub *Subject, hosts []string) (template *x509.Certificate, err error) {
csr, err := x509.ParseCertificateRequest(csrBytes)
if err != nil {
err = cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, err)
return
}
err = CheckSignature(csr, csr.SignatureAlgorithm, csr.RawTBSCertificateRequest, csr.Signature)
if err != nil {
err = cferr.Wrap(cferr.CertificateError, cferr.KeyMismatch, err)
return
}
template = &x509.Certificate{
Subject: csr.Subject,
PublicKeyAlgorithm: csr.PublicKeyAlgorithm,
PublicKey: csr.PublicKey,
SignatureAlgorithm: s.SigAlgo(),
}
if sub != nil {
template.Subject = sub.Name()
}
for i := range hosts {
if ip := net.ParseIP(hosts[i]); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, hosts[i])
}
}
return
}
// CheckSignature verifies a signature made by the key on a CSR, such
// as on the CSR itself.
func CheckSignature(csr *x509.CertificateRequest, algo x509.SignatureAlgorithm, signed, signature []byte) error {
var hashType crypto.Hash
switch algo {
case x509.SHA1WithRSA, x509.ECDSAWithSHA1:
hashType = crypto.SHA1
case x509.SHA256WithRSA, x509.ECDSAWithSHA256:
hashType = crypto.SHA256
case x509.SHA384WithRSA, x509.ECDSAWithSHA384:
hashType = crypto.SHA384
case x509.SHA512WithRSA, x509.ECDSAWithSHA512:
hashType = crypto.SHA512
default:
return x509.ErrUnsupportedAlgorithm
}
if !hashType.Available() {
return x509.ErrUnsupportedAlgorithm
}
h := hashType.New()
h.Write(signed)
digest := h.Sum(nil)
switch pub := csr.PublicKey.(type) {
case *rsa.PublicKey:
return rsa.VerifyPKCS1v15(pub, hashType, digest, signature)
case *ecdsa.PublicKey:
ecdsaSig := new(struct{ R, S *big.Int })
if _, err := asn1.Unmarshal(signature, ecdsaSig); err != nil {
return err
}
if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 {
return errors.New("x509: ECDSA signature contained zero or negative values")
}
if !ecdsa.Verify(pub, digest, ecdsaSig.R, ecdsaSig.S) {
return errors.New("x509: ECDSA verification failure")
}
return nil
}
return x509.ErrUnsupportedAlgorithm
}
type subjectPublicKeyInfo struct {
Algorithm pkix.AlgorithmIdentifier
SubjectPublicKey asn1.BitString
}
// ComputeSKI derives an SKI from the certificate's public key in a
// standard manner. This is done by computing the SHA-1 digest of the
// SubjectPublicKeyInfo component of the certificate.
func ComputeSKI(template *x509.Certificate) ([]byte, error) {
pub := template.PublicKey
encodedPub, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return nil, err
}
var subPKI subjectPublicKeyInfo
_, err = asn1.Unmarshal(encodedPub, &subPKI)
if err != nil {
return nil, err
}
pubHash := sha1.Sum(subPKI.SubjectPublicKey.Bytes)
return pubHash[:], nil
}
// FillTemplate is a utility function that tries to load as much of
// the certificate template as possible from the profiles and current
// template. It fills in the key uses, expiration, revocation URLs,
// serial number, and SKI.
func FillTemplate(template *x509.Certificate, defaultProfile, profile *config.SigningProfile) error {
ski, err := ComputeSKI(template)
var (
eku []x509.ExtKeyUsage
ku x509.KeyUsage
backdate time.Duration
expiry time.Duration
notBefore time.Time
notAfter time.Time
crlURL, ocspURL string
)
// The third value returned from Usages is a list of unknown key usages.
// This should be used when validating the profile at load, and isn't used
// here.
ku, eku, _ = profile.Usages()
if profile.IssuerURL == nil {
profile.IssuerURL = defaultProfile.IssuerURL
}
if ku == 0 && len(eku) == 0 {
return cferr.New(cferr.PolicyError, cferr.NoKeyUsages)
}
if expiry = profile.Expiry; expiry == 0 {
expiry = defaultProfile.Expiry
}
if crlURL = profile.CRL; crlURL == "" {
crlURL = defaultProfile.CRL
}
if ocspURL = profile.OCSP; ocspURL == "" {
ocspURL = defaultProfile.OCSP
}
if backdate = profile.Backdate; backdate == 0 {
backdate = -5 * time.Minute
} else {
backdate = -1 * profile.Backdate
}
if !profile.NotBefore.IsZero() {
notBefore = profile.NotBefore.UTC()
} else {
notBefore = time.Now().Round(time.Minute).Add(backdate).UTC()
}
if !profile.NotAfter.IsZero() {
notAfter = profile.NotAfter.UTC()
} else {
notAfter = notBefore.Add(expiry).UTC()
}
serialNumber, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
if err != nil {
return cferr.Wrap(cferr.CertificateError, cferr.Unknown, err)
}
template.SerialNumber = serialNumber
template.NotBefore = notBefore
template.NotAfter = notAfter
template.KeyUsage = ku
template.ExtKeyUsage = eku
template.BasicConstraintsValid = true
template.IsCA = profile.CA
template.SubjectKeyId = ski
if ocspURL != "" {
template.OCSPServer = []string{ocspURL}
}
if crlURL != "" {
template.CRLDistributionPoints = []string{crlURL}
}
if len(profile.IssuerURL) != 0 {
template.IssuingCertificateURL = profile.IssuerURL
}
if len(profile.Policies) != 0 {
template.PolicyIdentifiers = profile.Policies
}
if profile.OCSPNoCheck {
ocspNoCheckExtension := pkix.Extension{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1, 5},
Critical: false,
Value: []byte{0x05, 0x00},
}
template.ExtraExtensions = append(template.ExtraExtensions, ocspNoCheckExtension)
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More