Big dependency update, all lowercase sirupsen's for all dependencies.

This commit is contained in:
Travis Reeder
2017-08-23 19:52:56 -07:00
parent f559acd7ed
commit d7bf64bf66
6149 changed files with 870816 additions and 184795 deletions

View File

@@ -1,59 +0,0 @@
package main
import (
"github.com/Sirupsen/logrus"
// "os"
)
var log = logrus.New()
func init() {
log.Formatter = new(logrus.JSONFormatter)
log.Formatter = new(logrus.TextFormatter) // default
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
// if err == nil {
// log.Out = file
// } else {
// log.Info("Failed to log to file, using default stderr")
// }
log.Level = logrus.DebugLevel
}
func main() {
defer func() {
err := recover()
if err != nil {
log.WithFields(logrus.Fields{
"omg": true,
"err": err,
"number": 100,
}).Fatal("The ice breaks!")
}
}()
log.WithFields(logrus.Fields{
"animal": "walrus",
"number": 8,
}).Debug("Started observing beach")
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"temperature": -4,
}).Debug("Temperature changes")
log.WithFields(logrus.Fields{
"animal": "orca",
"size": 9009,
}).Panic("It's over 9000!")
}

View File

@@ -1,30 +0,0 @@
package main
import (
"github.com/Sirupsen/logrus"
"gopkg.in/gemnasium/logrus-airbrake-hook.v2"
)
var log = logrus.New()
func init() {
log.Formatter = new(logrus.TextFormatter) // default
log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
}
func main() {
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"omg": true,
"number": 100,
}).Fatal("The ice breaks!")
}

View File

@@ -1,10 +0,0 @@
// +build appengine
package logrus
import "io"
// IsTerminal returns true if stderr's file descriptor is a terminal.
func IsTerminal(f io.Writer) bool {
return true
}

View File

@@ -1,28 +0,0 @@
// Based on ssh/terminal:
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux darwin freebsd openbsd netbsd dragonfly
// +build !appengine
package logrus
import (
"io"
"os"
"syscall"
"unsafe"
)
// IsTerminal returns true if stderr's file descriptor is a terminal.
func IsTerminal(f io.Writer) bool {
var termios Termios
switch v := f.(type) {
case *os.File:
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(v.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0
default:
return false
}
}

View File

@@ -1,21 +0,0 @@
// +build solaris,!appengine
package logrus
import (
"io"
"os"
"golang.org/x/sys/unix"
)
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(f io.Writer) bool {
switch v := f.(type) {
case *os.File:
_, err := unix.IoctlGetTermios(int(v.Fd()), unix.TCGETA)
return err == nil
default:
return false
}
}

View File

@@ -1,33 +0,0 @@
// Based on ssh/terminal:
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows,!appengine
package logrus
import (
"io"
"os"
"syscall"
"unsafe"
)
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var (
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
)
// IsTerminal returns true if stderr's file descriptor is a terminal.
func IsTerminal(f io.Writer) bool {
switch v := f.(type) {
case *os.File:
var st uint32
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(v.Fd()), uintptr(unsafe.Pointer(&st)), 0)
return r != 0 && e == 0
default:
return false
}
}

2
vendor/github.com/beorn7/perks/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
*.test
*.prof

20
vendor/github.com/beorn7/perks/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,20 @@
Copyright (C) 2013 Blake Mizerany
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

31
vendor/github.com/beorn7/perks/README.md generated vendored Normal file
View File

@@ -0,0 +1,31 @@
# Perks for Go (golang.org)
Perks contains the Go package quantile that computes approximate quantiles over
an unbounded data stream within low memory and CPU bounds.
For more information and examples, see:
http://godoc.org/github.com/bmizerany/perks
A very special thank you and shout out to Graham Cormode (Rutgers University),
Flip Korn (AT&T LabsResearch), S. Muthukrishnan (Rutgers University), and
Divesh Srivastava (AT&T LabsResearch) for their research and publication of
[Effective Computation of Biased Quantiles over Data Streams](http://www.cs.rutgers.edu/~muthu/bquant.pdf)
Thank you, also:
* Armon Dadgar (@armon)
* Andrew Gerrand (@nf)
* Brad Fitzpatrick (@bradfitz)
* Keith Rarick (@kr)
FAQ:
Q: Why not move the quantile package into the project root?
A: I want to add more packages to perks later.
Copyright (C) 2013 Blake Mizerany
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

26
vendor/github.com/beorn7/perks/histogram/bench_test.go generated vendored Normal file
View File

@@ -0,0 +1,26 @@
package histogram
import (
"math/rand"
"testing"
)
func BenchmarkInsert10Bins(b *testing.B) {
b.StopTimer()
h := New(10)
b.StartTimer()
for i := 0; i < b.N; i++ {
f := rand.ExpFloat64()
h.Insert(f)
}
}
func BenchmarkInsert100Bins(b *testing.B) {
b.StopTimer()
h := New(100)
b.StartTimer()
for i := 0; i < b.N; i++ {
f := rand.ExpFloat64()
h.Insert(f)
}
}

108
vendor/github.com/beorn7/perks/histogram/histogram.go generated vendored Normal file
View File

@@ -0,0 +1,108 @@
// Package histogram provides a Go implementation of BigML's histogram package
// for Clojure/Java. It is currently experimental.
package histogram
import (
"container/heap"
"math"
"sort"
)
type Bin struct {
Count int
Sum float64
}
func (b *Bin) Update(x *Bin) {
b.Count += x.Count
b.Sum += x.Sum
}
func (b *Bin) Mean() float64 {
return b.Sum / float64(b.Count)
}
type Bins []*Bin
func (bs Bins) Len() int { return len(bs) }
func (bs Bins) Less(i, j int) bool { return bs[i].Mean() < bs[j].Mean() }
func (bs Bins) Swap(i, j int) { bs[i], bs[j] = bs[j], bs[i] }
func (bs *Bins) Push(x interface{}) {
*bs = append(*bs, x.(*Bin))
}
func (bs *Bins) Pop() interface{} {
return bs.remove(len(*bs) - 1)
}
func (bs *Bins) remove(n int) *Bin {
if n < 0 || len(*bs) < n {
return nil
}
x := (*bs)[n]
*bs = append((*bs)[:n], (*bs)[n+1:]...)
return x
}
type Histogram struct {
res *reservoir
}
func New(maxBins int) *Histogram {
return &Histogram{res: newReservoir(maxBins)}
}
func (h *Histogram) Insert(f float64) {
h.res.insert(&Bin{1, f})
h.res.compress()
}
func (h *Histogram) Bins() Bins {
return h.res.bins
}
type reservoir struct {
n int
maxBins int
bins Bins
}
func newReservoir(maxBins int) *reservoir {
return &reservoir{maxBins: maxBins}
}
func (r *reservoir) insert(bin *Bin) {
r.n += bin.Count
i := sort.Search(len(r.bins), func(i int) bool {
return r.bins[i].Mean() >= bin.Mean()
})
if i < 0 || i == r.bins.Len() {
// TODO(blake): Maybe use an .insert(i, bin) instead of
// performing the extra work of a heap.Push.
heap.Push(&r.bins, bin)
return
}
r.bins[i].Update(bin)
}
func (r *reservoir) compress() {
for r.bins.Len() > r.maxBins {
minGapIndex := -1
minGap := math.MaxFloat64
for i := 0; i < r.bins.Len()-1; i++ {
gap := gapWeight(r.bins[i], r.bins[i+1])
if minGap > gap {
minGap = gap
minGapIndex = i
}
}
prev := r.bins[minGapIndex]
next := r.bins.remove(minGapIndex + 1)
prev.Update(next)
}
}
func gapWeight(prev, next *Bin) float64 {
return next.Mean() - prev.Mean()
}

View File

@@ -0,0 +1,38 @@
package histogram
import (
"math/rand"
"testing"
)
func TestHistogram(t *testing.T) {
const numPoints = 1e6
const maxBins = 3
h := New(maxBins)
for i := 0; i < numPoints; i++ {
f := rand.ExpFloat64()
h.Insert(f)
}
bins := h.Bins()
if g := len(bins); g > maxBins {
t.Fatalf("got %d bins, wanted <= %d", g, maxBins)
}
for _, b := range bins {
t.Logf("%+v", b)
}
if g := count(h.Bins()); g != numPoints {
t.Fatalf("binned %d points, wanted %d", g, numPoints)
}
}
func count(bins Bins) int {
binCounts := 0
for _, b := range bins {
binCounts += b.Count
}
return binCounts
}

63
vendor/github.com/beorn7/perks/quantile/bench_test.go generated vendored Normal file
View File

@@ -0,0 +1,63 @@
package quantile
import (
"testing"
)
func BenchmarkInsertTargeted(b *testing.B) {
b.ReportAllocs()
s := NewTargeted(Targets)
b.ResetTimer()
for i := float64(0); i < float64(b.N); i++ {
s.Insert(i)
}
}
func BenchmarkInsertTargetedSmallEpsilon(b *testing.B) {
s := NewTargeted(TargetsSmallEpsilon)
b.ResetTimer()
for i := float64(0); i < float64(b.N); i++ {
s.Insert(i)
}
}
func BenchmarkInsertBiased(b *testing.B) {
s := NewLowBiased(0.01)
b.ResetTimer()
for i := float64(0); i < float64(b.N); i++ {
s.Insert(i)
}
}
func BenchmarkInsertBiasedSmallEpsilon(b *testing.B) {
s := NewLowBiased(0.0001)
b.ResetTimer()
for i := float64(0); i < float64(b.N); i++ {
s.Insert(i)
}
}
func BenchmarkQuery(b *testing.B) {
s := NewTargeted(Targets)
for i := float64(0); i < 1e6; i++ {
s.Insert(i)
}
b.ResetTimer()
n := float64(b.N)
for i := float64(0); i < n; i++ {
s.Query(i / n)
}
}
func BenchmarkQuerySmallEpsilon(b *testing.B) {
s := NewTargeted(TargetsSmallEpsilon)
for i := float64(0); i < 1e6; i++ {
s.Insert(i)
}
b.ResetTimer()
n := float64(b.N)
for i := float64(0); i < n; i++ {
s.Query(i / n)
}
}

121
vendor/github.com/beorn7/perks/quantile/example_test.go generated vendored Normal file
View File

@@ -0,0 +1,121 @@
// +build go1.1
package quantile_test
import (
"bufio"
"fmt"
"log"
"os"
"strconv"
"time"
"github.com/beorn7/perks/quantile"
)
func Example_simple() {
ch := make(chan float64)
go sendFloats(ch)
// Compute the 50th, 90th, and 99th percentile.
q := quantile.NewTargeted(map[float64]float64{
0.50: 0.005,
0.90: 0.001,
0.99: 0.0001,
})
for v := range ch {
q.Insert(v)
}
fmt.Println("perc50:", q.Query(0.50))
fmt.Println("perc90:", q.Query(0.90))
fmt.Println("perc99:", q.Query(0.99))
fmt.Println("count:", q.Count())
// Output:
// perc50: 5
// perc90: 16
// perc99: 223
// count: 2388
}
func Example_mergeMultipleStreams() {
// Scenario:
// We have multiple database shards. On each shard, there is a process
// collecting query response times from the database logs and inserting
// them into a Stream (created via NewTargeted(0.90)), much like the
// Simple example. These processes expose a network interface for us to
// ask them to serialize and send us the results of their
// Stream.Samples so we may Merge and Query them.
//
// NOTES:
// * These sample sets are small, allowing us to get them
// across the network much faster than sending the entire list of data
// points.
//
// * For this to work correctly, we must supply the same quantiles
// a priori the process collecting the samples supplied to NewTargeted,
// even if we do not plan to query them all here.
ch := make(chan quantile.Samples)
getDBQuerySamples(ch)
q := quantile.NewTargeted(map[float64]float64{0.90: 0.001})
for samples := range ch {
q.Merge(samples)
}
fmt.Println("perc90:", q.Query(0.90))
}
func Example_window() {
// Scenario: We want the 90th, 95th, and 99th percentiles for each
// minute.
ch := make(chan float64)
go sendStreamValues(ch)
tick := time.NewTicker(1 * time.Minute)
q := quantile.NewTargeted(map[float64]float64{
0.90: 0.001,
0.95: 0.0005,
0.99: 0.0001,
})
for {
select {
case t := <-tick.C:
flushToDB(t, q.Samples())
q.Reset()
case v := <-ch:
q.Insert(v)
}
}
}
func sendStreamValues(ch chan float64) {
// Use your imagination
}
func flushToDB(t time.Time, samples quantile.Samples) {
// Use your imagination
}
// This is a stub for the above example. In reality this would hit the remote
// servers via http or something like it.
func getDBQuerySamples(ch chan quantile.Samples) {}
func sendFloats(ch chan<- float64) {
f, err := os.Open("exampledata.txt")
if err != nil {
log.Fatal(err)
}
sc := bufio.NewScanner(f)
for sc.Scan() {
b := sc.Bytes()
v, err := strconv.ParseFloat(string(b), 64)
if err != nil {
log.Fatal(err)
}
ch <- v
}
if sc.Err() != nil {
log.Fatal(sc.Err())
}
close(ch)
}

2388
vendor/github.com/beorn7/perks/quantile/exampledata.txt generated vendored Normal file

File diff suppressed because it is too large Load Diff

292
vendor/github.com/beorn7/perks/quantile/stream.go generated vendored Normal file
View File

@@ -0,0 +1,292 @@
// Package quantile computes approximate quantiles over an unbounded data
// stream within low memory and CPU bounds.
//
// A small amount of accuracy is traded to achieve the above properties.
//
// Multiple streams can be merged before calling Query to generate a single set
// of results. This is meaningful when the streams represent the same type of
// data. See Merge and Samples.
//
// For more detailed information about the algorithm used, see:
//
// Effective Computation of Biased Quantiles over Data Streams
//
// http://www.cs.rutgers.edu/~muthu/bquant.pdf
package quantile
import (
"math"
"sort"
)
// Sample holds an observed value and meta information for compression. JSON
// tags have been added for convenience.
type Sample struct {
Value float64 `json:",string"`
Width float64 `json:",string"`
Delta float64 `json:",string"`
}
// Samples represents a slice of samples. It implements sort.Interface.
type Samples []Sample
func (a Samples) Len() int { return len(a) }
func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value }
func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type invariant func(s *stream, r float64) float64
// NewLowBiased returns an initialized Stream for low-biased quantiles
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
// error guarantees can still be given even for the lower ranks of the data
// distribution.
//
// The provided epsilon is a relative error, i.e. the true quantile of a value
// returned by a query is guaranteed to be within (1±Epsilon)*Quantile.
//
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
// properties.
func NewLowBiased(epsilon float64) *Stream {
ƒ := func(s *stream, r float64) float64 {
return 2 * epsilon * r
}
return newStream(ƒ)
}
// NewHighBiased returns an initialized Stream for high-biased quantiles
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
// error guarantees can still be given even for the higher ranks of the data
// distribution.
//
// The provided epsilon is a relative error, i.e. the true quantile of a value
// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile).
//
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
// properties.
func NewHighBiased(epsilon float64) *Stream {
ƒ := func(s *stream, r float64) float64 {
return 2 * epsilon * (s.n - r)
}
return newStream(ƒ)
}
// NewTargeted returns an initialized Stream concerned with a particular set of
// quantile values that are supplied a priori. Knowing these a priori reduces
// space and computation time. The targets map maps the desired quantiles to
// their absolute errors, i.e. the true quantile of a value returned by a query
// is guaranteed to be within (Quantile±Epsilon).
//
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
func NewTargeted(targets map[float64]float64) *Stream {
ƒ := func(s *stream, r float64) float64 {
var m = math.MaxFloat64
var f float64
for quantile, epsilon := range targets {
if quantile*s.n <= r {
f = (2 * epsilon * r) / quantile
} else {
f = (2 * epsilon * (s.n - r)) / (1 - quantile)
}
if f < m {
m = f
}
}
return m
}
return newStream(ƒ)
}
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
// design. Take care when using across multiple goroutines.
type Stream struct {
*stream
b Samples
sorted bool
}
func newStream(ƒ invariant) *Stream {
x := &stream{ƒ: ƒ}
return &Stream{x, make(Samples, 0, 500), true}
}
// Insert inserts v into the stream.
func (s *Stream) Insert(v float64) {
s.insert(Sample{Value: v, Width: 1})
}
func (s *Stream) insert(sample Sample) {
s.b = append(s.b, sample)
s.sorted = false
if len(s.b) == cap(s.b) {
s.flush()
}
}
// Query returns the computed qth percentiles value. If s was created with
// NewTargeted, and q is not in the set of quantiles provided a priori, Query
// will return an unspecified result.
func (s *Stream) Query(q float64) float64 {
if !s.flushed() {
// Fast path when there hasn't been enough data for a flush;
// this also yields better accuracy for small sets of data.
l := len(s.b)
if l == 0 {
return 0
}
i := int(math.Ceil(float64(l) * q))
if i > 0 {
i -= 1
}
s.maybeSort()
return s.b[i].Value
}
s.flush()
return s.stream.query(q)
}
// Merge merges samples into the underlying streams samples. This is handy when
// merging multiple streams from separate threads, database shards, etc.
//
// ATTENTION: This method is broken and does not yield correct results. The
// underlying algorithm is not capable of merging streams correctly.
func (s *Stream) Merge(samples Samples) {
sort.Sort(samples)
s.stream.merge(samples)
}
// Reset reinitializes and clears the list reusing the samples buffer memory.
func (s *Stream) Reset() {
s.stream.reset()
s.b = s.b[:0]
}
// Samples returns stream samples held by s.
func (s *Stream) Samples() Samples {
if !s.flushed() {
return s.b
}
s.flush()
return s.stream.samples()
}
// Count returns the total number of samples observed in the stream
// since initialization.
func (s *Stream) Count() int {
return len(s.b) + s.stream.count()
}
func (s *Stream) flush() {
s.maybeSort()
s.stream.merge(s.b)
s.b = s.b[:0]
}
func (s *Stream) maybeSort() {
if !s.sorted {
s.sorted = true
sort.Sort(s.b)
}
}
func (s *Stream) flushed() bool {
return len(s.stream.l) > 0
}
type stream struct {
n float64
l []Sample
ƒ invariant
}
func (s *stream) reset() {
s.l = s.l[:0]
s.n = 0
}
func (s *stream) insert(v float64) {
s.merge(Samples{{v, 1, 0}})
}
func (s *stream) merge(samples Samples) {
// TODO(beorn7): This tries to merge not only individual samples, but
// whole summaries. The paper doesn't mention merging summaries at
// all. Unittests show that the merging is inaccurate. Find out how to
// do merges properly.
var r float64
i := 0
for _, sample := range samples {
for ; i < len(s.l); i++ {
c := s.l[i]
if c.Value > sample.Value {
// Insert at position i.
s.l = append(s.l, Sample{})
copy(s.l[i+1:], s.l[i:])
s.l[i] = Sample{
sample.Value,
sample.Width,
math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1),
// TODO(beorn7): How to calculate delta correctly?
}
i++
goto inserted
}
r += c.Width
}
s.l = append(s.l, Sample{sample.Value, sample.Width, 0})
i++
inserted:
s.n += sample.Width
r += sample.Width
}
s.compress()
}
func (s *stream) count() int {
return int(s.n)
}
func (s *stream) query(q float64) float64 {
t := math.Ceil(q * s.n)
t += math.Ceil(s.ƒ(s, t) / 2)
p := s.l[0]
var r float64
for _, c := range s.l[1:] {
r += p.Width
if r+c.Width+c.Delta > t {
return p.Value
}
p = c
}
return p.Value
}
func (s *stream) compress() {
if len(s.l) < 2 {
return
}
x := s.l[len(s.l)-1]
xi := len(s.l) - 1
r := s.n - 1 - x.Width
for i := len(s.l) - 2; i >= 0; i-- {
c := s.l[i]
if c.Width+x.Width+x.Delta <= s.ƒ(s, r) {
x.Width += c.Width
s.l[xi] = x
// Remove element at i.
copy(s.l[i:], s.l[i+1:])
s.l = s.l[:len(s.l)-1]
xi -= 1
} else {
x = c
xi = i
}
r -= c.Width
}
}
func (s *stream) samples() Samples {
samples := make(Samples, len(s.l))
copy(samples, s.l)
return samples
}

215
vendor/github.com/beorn7/perks/quantile/stream_test.go generated vendored Normal file
View File

@@ -0,0 +1,215 @@
package quantile
import (
"math"
"math/rand"
"sort"
"testing"
)
var (
Targets = map[float64]float64{
0.01: 0.001,
0.10: 0.01,
0.50: 0.05,
0.90: 0.01,
0.99: 0.001,
}
TargetsSmallEpsilon = map[float64]float64{
0.01: 0.0001,
0.10: 0.001,
0.50: 0.005,
0.90: 0.001,
0.99: 0.0001,
}
LowQuantiles = []float64{0.01, 0.1, 0.5}
HighQuantiles = []float64{0.99, 0.9, 0.5}
)
const RelativeEpsilon = 0.01
func verifyPercsWithAbsoluteEpsilon(t *testing.T, a []float64, s *Stream) {
sort.Float64s(a)
for quantile, epsilon := range Targets {
n := float64(len(a))
k := int(quantile * n)
if k < 1 {
k = 1
}
lower := int((quantile - epsilon) * n)
if lower < 1 {
lower = 1
}
upper := int(math.Ceil((quantile + epsilon) * n))
if upper > len(a) {
upper = len(a)
}
w, min, max := a[k-1], a[lower-1], a[upper-1]
if g := s.Query(quantile); g < min || g > max {
t.Errorf("q=%f: want %v [%f,%f], got %v", quantile, w, min, max, g)
}
}
}
func verifyLowPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) {
sort.Float64s(a)
for _, qu := range LowQuantiles {
n := float64(len(a))
k := int(qu * n)
lowerRank := int((1 - RelativeEpsilon) * qu * n)
upperRank := int(math.Ceil((1 + RelativeEpsilon) * qu * n))
w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1]
if g := s.Query(qu); g < min || g > max {
t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g)
}
}
}
func verifyHighPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) {
sort.Float64s(a)
for _, qu := range HighQuantiles {
n := float64(len(a))
k := int(qu * n)
lowerRank := int((1 - (1+RelativeEpsilon)*(1-qu)) * n)
upperRank := int(math.Ceil((1 - (1-RelativeEpsilon)*(1-qu)) * n))
w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1]
if g := s.Query(qu); g < min || g > max {
t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g)
}
}
}
func populateStream(s *Stream) []float64 {
a := make([]float64, 0, 1e5+100)
for i := 0; i < cap(a); i++ {
v := rand.NormFloat64()
// Add 5% asymmetric outliers.
if i%20 == 0 {
v = v*v + 1
}
s.Insert(v)
a = append(a, v)
}
return a
}
func TestTargetedQuery(t *testing.T) {
rand.Seed(42)
s := NewTargeted(Targets)
a := populateStream(s)
verifyPercsWithAbsoluteEpsilon(t, a, s)
}
func TestTargetedQuerySmallSampleSize(t *testing.T) {
rand.Seed(42)
s := NewTargeted(TargetsSmallEpsilon)
a := []float64{1, 2, 3, 4, 5}
for _, v := range a {
s.Insert(v)
}
verifyPercsWithAbsoluteEpsilon(t, a, s)
// If not yet flushed, results should be precise:
if !s.flushed() {
for φ, want := range map[float64]float64{
0.01: 1,
0.10: 1,
0.50: 3,
0.90: 5,
0.99: 5,
} {
if got := s.Query(φ); got != want {
t.Errorf("want %f for φ=%f, got %f", want, φ, got)
}
}
}
}
func TestLowBiasedQuery(t *testing.T) {
rand.Seed(42)
s := NewLowBiased(RelativeEpsilon)
a := populateStream(s)
verifyLowPercsWithRelativeEpsilon(t, a, s)
}
func TestHighBiasedQuery(t *testing.T) {
rand.Seed(42)
s := NewHighBiased(RelativeEpsilon)
a := populateStream(s)
verifyHighPercsWithRelativeEpsilon(t, a, s)
}
// BrokenTestTargetedMerge is broken, see Merge doc comment.
func BrokenTestTargetedMerge(t *testing.T) {
rand.Seed(42)
s1 := NewTargeted(Targets)
s2 := NewTargeted(Targets)
a := populateStream(s1)
a = append(a, populateStream(s2)...)
s1.Merge(s2.Samples())
verifyPercsWithAbsoluteEpsilon(t, a, s1)
}
// BrokenTestLowBiasedMerge is broken, see Merge doc comment.
func BrokenTestLowBiasedMerge(t *testing.T) {
rand.Seed(42)
s1 := NewLowBiased(RelativeEpsilon)
s2 := NewLowBiased(RelativeEpsilon)
a := populateStream(s1)
a = append(a, populateStream(s2)...)
s1.Merge(s2.Samples())
verifyLowPercsWithRelativeEpsilon(t, a, s2)
}
// BrokenTestHighBiasedMerge is broken, see Merge doc comment.
func BrokenTestHighBiasedMerge(t *testing.T) {
rand.Seed(42)
s1 := NewHighBiased(RelativeEpsilon)
s2 := NewHighBiased(RelativeEpsilon)
a := populateStream(s1)
a = append(a, populateStream(s2)...)
s1.Merge(s2.Samples())
verifyHighPercsWithRelativeEpsilon(t, a, s2)
}
func TestUncompressed(t *testing.T) {
q := NewTargeted(Targets)
for i := 100; i > 0; i-- {
q.Insert(float64(i))
}
if g := q.Count(); g != 100 {
t.Errorf("want count 100, got %d", g)
}
// Before compression, Query should have 100% accuracy.
for quantile := range Targets {
w := quantile * 100
if g := q.Query(quantile); g != w {
t.Errorf("want %f, got %f", w, g)
}
}
}
func TestUncompressedSamples(t *testing.T) {
q := NewTargeted(map[float64]float64{0.99: 0.001})
for i := 1; i <= 100; i++ {
q.Insert(float64(i))
}
if g := q.Samples().Len(); g != 100 {
t.Errorf("want count 100, got %d", g)
}
}
func TestUncompressedOne(t *testing.T) {
q := NewTargeted(map[float64]float64{0.99: 0.01})
q.Insert(3.14)
if g := q.Query(0.90); g != 3.14 {
t.Error("want PI, got", g)
}
}
func TestDefaults(t *testing.T) {
if g := NewTargeted(map[float64]float64{0.99: 0.001}).Query(0.99); g != 0 {
t.Errorf("want 0, got %f", g)
}
}

90
vendor/github.com/beorn7/perks/topk/topk.go generated vendored Normal file
View File

@@ -0,0 +1,90 @@
package topk
import (
"sort"
)
// http://www.cs.ucsb.edu/research/tech_reports/reports/2005-23.pdf
type Element struct {
Value string
Count int
}
type Samples []*Element
func (sm Samples) Len() int {
return len(sm)
}
func (sm Samples) Less(i, j int) bool {
return sm[i].Count < sm[j].Count
}
func (sm Samples) Swap(i, j int) {
sm[i], sm[j] = sm[j], sm[i]
}
type Stream struct {
k int
mon map[string]*Element
// the minimum Element
min *Element
}
func New(k int) *Stream {
s := new(Stream)
s.k = k
s.mon = make(map[string]*Element)
s.min = &Element{}
// Track k+1 so that less frequenet items contended for that spot,
// resulting in k being more accurate.
return s
}
func (s *Stream) Insert(x string) {
s.insert(&Element{x, 1})
}
func (s *Stream) Merge(sm Samples) {
for _, e := range sm {
s.insert(e)
}
}
func (s *Stream) insert(in *Element) {
e := s.mon[in.Value]
if e != nil {
e.Count++
} else {
if len(s.mon) < s.k+1 {
e = &Element{in.Value, in.Count}
s.mon[in.Value] = e
} else {
e = s.min
delete(s.mon, e.Value)
e.Value = in.Value
e.Count += in.Count
s.min = e
}
}
if e.Count < s.min.Count {
s.min = e
}
}
func (s *Stream) Query() Samples {
var sm Samples
for _, e := range s.mon {
sm = append(sm, e)
}
sort.Sort(sort.Reverse(sm))
if len(sm) < s.k {
return sm
}
return sm[:s.k]
}

57
vendor/github.com/beorn7/perks/topk/topk_test.go generated vendored Normal file
View File

@@ -0,0 +1,57 @@
package topk
import (
"fmt"
"math/rand"
"sort"
"testing"
)
func TestTopK(t *testing.T) {
stream := New(10)
ss := []*Stream{New(10), New(10), New(10)}
m := make(map[string]int)
for _, s := range ss {
for i := 0; i < 1e6; i++ {
v := fmt.Sprintf("%x", int8(rand.ExpFloat64()))
s.Insert(v)
m[v]++
}
stream.Merge(s.Query())
}
var sm Samples
for x, s := range m {
sm = append(sm, &Element{x, s})
}
sort.Sort(sort.Reverse(sm))
g := stream.Query()
if len(g) != 10 {
t.Fatalf("got %d, want 10", len(g))
}
for i, e := range g {
if sm[i].Value != e.Value {
t.Errorf("at %d: want %q, got %q", i, sm[i].Value, e.Value)
}
}
}
func TestQuery(t *testing.T) {
queryTests := []struct {
value string
expected int
}{
{"a", 1},
{"b", 2},
{"c", 2},
}
stream := New(2)
for _, tt := range queryTests {
stream.Insert(tt.value)
if n := len(stream.Query()); n != tt.expected {
t.Errorf("want %d, got %d", tt.expected, n)
}
}
}

4
vendor/github.com/cloudflare/cfssl/.dockerignore generated vendored Normal file
View File

@@ -0,0 +1,4 @@
cfssl_*
*-amd64
*-386
dist/*

4
vendor/github.com/cloudflare/cfssl/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,4 @@
dist/*
cli/serve/static.rice-box.go
.coverprofile
gopath

77
vendor/github.com/cloudflare/cfssl/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,77 @@
sudo: false
language: go
go:
- 1.7.x
- 1.8.x
# Install g++-4.8 to support std=c++11 for github.com/google/certificate-transparency/go/merkletree
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
install:
- if [ "$CXX" = "g++" ]; then export CXX="g++-4.8"; fi
# Used by the certdb tests
services:
- mysql
- postgresql
before_install:
# CFSSL consists of multiple Go packages, which refer to each other by
# their absolute GitHub path, e.g. github.com/cloudflare/crypto/pkcs11key.
# That means, by default, if someone forks the repo and makes changes across
# multiple packages within CFSSL, Travis won't pass for the branch on their
# own repo. To fix that, we move the directory
- mkdir -p $TRAVIS_BUILD_DIR $GOPATH/src/github.com/cloudflare
- test ! -d $GOPATH/src/github.com/cloudflare/cfssl && mv $TRAVIS_BUILD_DIR $GOPATH/src/github.com/cloudflare/cfssl || true
# Only build pull requests, pushes to the master branch, and branches
# starting with `test-`. This is a convenient way to push branches to
# your own fork of the repostiory to ensure Travis passes before submitting
# a PR. For instance, you might run:
# git push myremote branchname:test-branchname
branches:
only:
- master
- /^test-.*$/
before_script:
- go get golang.org/x/tools/cmd/goimports
- go get github.com/onsi/gomega
- go get github.com/onsi/ginkgo
- go get -u github.com/golang/lint/golint
- go get github.com/modocache/gover
- go get -v github.com/GeertJohan/fgt
- go get -u honnef.co/go/tools/cmd/staticcheck
# Setup DBs + run migrations
- go get bitbucket.org/liamstask/goose/cmd/goose
- if [[ $(uname -s) == 'Linux' ]]; then
psql -c 'create database certdb_development;' -U postgres;
goose -path $GOPATH/src/github.com/cloudflare/cfssl/certdb/pg up;
mysql -e 'create database certdb_development;' -u root;
goose -path $GOPATH/src/github.com/cloudflare/cfssl/certdb/mysql up;
fi
script:
- ./test.sh
notifications:
email:
recipients:
- nick@cloudflare.com
- zi@cloudflare.com
- kyle@cloudflare.com
on_success: never
on_failure: change
env:
global:
- secure: "OmaaZ3jhU9VQ/0SYpenUJEfnmKy/MwExkefFRpDbkRSu/hTQpxxALAZV5WEHo7gxLRMRI0pytLo7w+lAd2FlX1CNcyY62MUicta/8P2twsxp+lR3v1bJ7dwk6qsDbO7Nvv3BKPCDQCHUkggbAEJaHEQGdLk4ursNEB1aGimuCEc="
- GO15VENDOREXPERIMENT=1
matrix:
- BUILD_TAGS="postgresql mysql"
matrix:
include:
- os: osx
go: 1.8.1
env: BUILD_TAGS=
after_success:
- bash <(curl -s https://codecov.io/bash) -f coverprofile.txt

38
vendor/github.com/cloudflare/cfssl/BUILDING.md generated vendored Normal file
View File

@@ -0,0 +1,38 @@
# How to Build CFSSL
## Docker
The requirements to build `CFSSL` are:
1. A running instance of Docker
2. The `bash` shell
To build, run:
$ script/build-docker
This is will build by default all the cfssl command line utilities
for darwin (OSX), linux, and windows for i386 and amd64 and output the
binaries in the current path.
To build a specific platform and OS, run:
$ script/build-docker -os="darwin" -arch="amd64"
Note: for cross-compilation compatibility, the Docker build process will
build programs without PKCS #11.
## Without Docker
The requirements to build without Docker are:
1. Go version 1.5 is the minimum required version of Go. However, only Go 1.6+
is supported due to the test system not supporting Go 1.5.
2. A properly configured go environment
3. A properly configured GOPATH
4. With Go 1.5, you are required to set the environment variable
`GO15VENDOREXPERIMENT=1`.
Run:
$ go install github.com/cloudflare/cfssl/cmd/...

72
vendor/github.com/cloudflare/cfssl/CHANGELOG generated vendored Normal file
View File

@@ -0,0 +1,72 @@
1.1.0 - 2015-08-04
ADDED:
- Revocation now checks OCSP status.
- Authenticated endpoints are now supported using HMAC tags.
- Bundle can verify certificates against a domain or IP.
- OCSP subcommand has been added.
- PKCS #11 keys are now supported; this support is now the default.
- OCSP serving is now implemented.
- The multirootca tool is now available for multiple signing
keys via an authenticated API.
- A scan utility for checking the quality of a server's TLS
configuration.
- The certificate bundler now supports PKCS #7 and PKCS #12.
- An info endpoint has been added to retrieve the signers'
certificates.
- Signers can now use a serial sequence number for certificate
serial numbers; the default remains randomised serial numbers.
- CSR whitelisting allows the signer to explicitly distrust
certain fields in a CSR.
- Signing profiles can include certificate policies and their
qualifiers.
- The multirootca can use Red October-secured private keys.
- The multirootca can whitelist CSRs per-signer based on an
IP network whitelist.
- The signer can whitelist SANs and common names via a regular-
expression whitelist.
- Multiple fallback remote signers are now supported in the
cfssl server.
- A Docker build script has been provided to facilitate building
CFSSL for all supported platforms.
- The log package includes a new logging level, fatal, that
immediately exits with error after printing the log message.
CHANGED:
- CLI tool can read from standard input.
- The -f flag has been renamed to -config.
- Signers have been refactored into local and remote signers
under a single universal signer abstraction.
- The CLI subcommands have been refactored into separate
packages.
- Signing can now extract subject information from a CSR.
- Various improvements to the certificate ubiquity scoring,
such as accounting for SHA1 deprecation.
- The bundle CLI tool can set the intermediates directory that
newly found intermediates can be stored in.
- The CLI tools return exit code 1 on failure.
CONTRIBUTORS:
Alice Xia
Dan Rohr
Didier Smith
Dominic Luechinger
Erik Kristensen
Fabian Ruff
George Tankersley
Harald Wagener
Harry Harpham
Jacob H. Haven
Jacob Hoffman-Andrews
Joshua Kroll
Kyle Isom
Nick Sullivan
Peter Eckersley
Richard Barnes
Sophie Huang
Steve Rude
Tara Vancil
Terin Stock
Thomaz Leite
Travis Truman
Zi Lin

16
vendor/github.com/cloudflare/cfssl/Dockerfile generated vendored Normal file
View File

@@ -0,0 +1,16 @@
FROM golang:1.8.1
ENV USER root
WORKDIR /go/src/github.com/cloudflare/cfssl
COPY . .
# restore all deps and build
RUN go get github.com/GeertJohan/go.rice/rice && rice embed-go -i=./cli/serve && \
cp -R /go/src/github.com/cloudflare/cfssl/vendor/github.com/cloudflare/cfssl_trust /etc/cfssl && \
go install ./cmd/...
EXPOSE 8888
ENTRYPOINT ["cfssl"]
CMD ["--help"]

11
vendor/github.com/cloudflare/cfssl/Dockerfile.build generated vendored Normal file
View File

@@ -0,0 +1,11 @@
FROM golang:1.8.1
ENV USER root
WORKDIR /go/src/github.com/cloudflare/cfssl
COPY . .
# restore all deps and build
RUN go get github.com/mitchellh/gox
ENTRYPOINT ["gox"]

39
vendor/github.com/cloudflare/cfssl/Dockerfile.minimal generated vendored Normal file
View File

@@ -0,0 +1,39 @@
FROM alpine:3.5
ENV PATH /go/bin:/usr/local/go/bin:$PATH
ENV GOPATH /go
ENV USER root
COPY . /go/src/github.com/cloudflare/cfssl
RUN buildDeps=' \
go \
git \
gcc \
libc-dev \
libtool \
libgcc \
' \
set -x && \
apk update && \
apk add $buildDeps && \
cd /go/src/github.com/cloudflare/cfssl && \
go get github.com/GeertJohan/go.rice/rice && rice embed-go -i=./cli/serve && \
cp -R /go/src/github.com/cloudflare/cfssl/vendor/github.com/cloudflare/cfssl_trust /etc/cfssl && \
go build -o /usr/bin/cfssl ./cmd/cfssl && \
go build -o /usr/bin/cfssljson ./cmd/cfssljson && \
go build -o /usr/bin/mkbundle ./cmd/mkbundle && \
go build -o /usr/bin/multirootca ./cmd/multirootca && \
apk del $buildDeps && \
rm -rf /var/cache/apk/* && \
rm -rf /go && \
echo "Build complete."
VOLUME [ "/etc/cfssl" ]
WORKDIR /etc/cfssl
EXPOSE 8888
ENTRYPOINT ["cfssl"]
CMD ["--help"]

24
vendor/github.com/cloudflare/cfssl/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,24 @@
Copyright (c) 2014 CloudFlare Inc.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

447
vendor/github.com/cloudflare/cfssl/README.md generated vendored Normal file
View File

@@ -0,0 +1,447 @@
# CFSSL
[![Build Status](https://travis-ci.org/cloudflare/cfssl.svg?branch=master)](https://travis-ci.org/cloudflare/cfssl)
[![Coverage Status](http://codecov.io/github/cloudflare/cfssl/coverage.svg?branch=master)](http://codecov.io/github/cloudflare/cfssl?branch=master)
[![GoDoc](https://godoc.org/github.com/cloudflare/cfssl?status.svg)](https://godoc.org/github.com/cloudflare/cfssl)
## CloudFlare's PKI/TLS toolkit
CFSSL is CloudFlare's PKI/TLS swiss army knife. It is both a command line
tool and an HTTP API server for signing, verifying, and bundling TLS
certificates. It requires Go 1.6+ to build.
Note that certain linux distributions have certain algorithms removed
(RHEL-based distributions in particular), so the golang from the
official repositories will not work. Users of these distributions should
[install go manually](//golang.org/dl) to install CFSSL.
CFSSL consists of:
* a set of packages useful for building custom TLS PKI tools
* the `cfssl` program, which is the canonical command line utility
using the CFSSL packages.
* the `multirootca` program, which is a certificate authority server
that can use multiple signing keys.
* the `mkbundle` program is used to build certificate pool bundles.
* the `cfssljson` program, which takes the JSON output from the
`cfssl` and `multirootca` programs and writes certificates, keys,
CSRs, and bundles to disk.
### Building
See [BUILDING](BUILDING.md)
### Installation
Installation requires a
[working Go 1.6+ installation](http://golang.org/doc/install) and a
properly set `GOPATH`.
```
$ go get -u github.com/cloudflare/cfssl/cmd/cfssl
```
will download and build the CFSSL tool, installing it in
`$GOPATH/bin/cfssl`. To install the other utility programs that are in
this repo:
```
$ go get -u github.com/cloudflare/cfssl/cmd/...
```
This will download, build, and install `cfssl`, `cfssljson`, and
`mkbundle` into `$GOPATH/bin/`.
#### Installing pre-Go 1.6
With a Go 1.5 installation, CFSSL will still probably build. However,
the test system uses [`golint`](https://github.com/golang/lint), which
no longer works on Go 1.5. As our test suite can't cover Go 1.5 anymore,
we no longer support it.
Note that CFSSL makes use of vendored packages; in Go 1.5, the
`GO15VENDOREXPERIMENT` environment variable will need to be set, e.g.
```
export GO15VENDOREXPERIMENT=1
```
With a Go 1.4 or earlier installation, you won't be able to install the
latest version of CFSSL. However, you can checkout the `1.1.0` release
and build that.
```
git clone -b 1.1.0 https://github.com/cloudflare/cfssl.git $GOPATH/src/github.com/cloudflare/cfssl
go get github.com/cloudflare/cfssl/cmd/cfssl
```
### Using the Command Line Tool
The `cfssl` command line tool takes a command to specify what
operation it should carry out:
sign signs a certificate
bundle build a certificate bundle
genkey generate a private key and a certificate request
gencert generate a private key and a certificate
serve start the API server
version prints out the current version
selfsign generates a self-signed certificate
print-defaults print default configurations
Use "cfssl [command] -help" to find out more about a command.
The version command takes no arguments.
#### Signing
```
cfssl sign [-ca cert] [-ca-key key] [-hostname comma,separated,hostnames] csr [subject]
```
The csr is the client's certificate request. The `-ca` and `-ca-key`
flags are the CA's certificate and private key, respectively. By
default, they are "ca.pem" and "ca_key.pem". The `-hostname` is
a comma separated hostname list that overrides the DNS names and
IP address in the certificate SAN extension.
For example, assuming the CA's private key is in
`/etc/ssl/private/cfssl_key.pem` and the CA's certificate is in
`/etc/ssl/certs/cfssl.pem`, to sign the `cloudflare.pem` certificate
for cloudflare.com:
```
cfssl sign -ca /etc/ssl/certs/cfssl.pem \
-ca-key /etc/ssl/private/cfssl_key.pem \
-hostname cloudflare.com ./cloudflare.pem
```
It is also possible to specify csr through '-csr' flag. By doing so,
flag values take precedence and will overwrite the argument.
The subject is an optional file that contains subject information that
should be used in place of the information from the CSR. It should be
a JSON file with the type:
```json
{
"CN": "example.com",
"names": [
{
"C": "US",
"L": "San Francisco",
"O": "Internet Widgets, Inc.",
"OU": "WWW",
"ST": "California"
}
]
}
```
**N.B.** As of Go 1.7, self-signed certificates will not include
[the AKI](https://go.googlesource.com/go/+/b623b71509b2d24df915d5bc68602e1c6edf38ca).
#### Bundling
```
cfssl bundle [-ca-bundle bundle] [-int-bundle bundle] \
[-metadata metadata_file] [-flavor bundle_flavor] \
-cert certificate_file [-key key_file]
```
The bundles are used for the root and intermediate certificate
pools. In addition, platform metadata is specified through '-metadata'
The bundle files, metadata file (and auxiliary files) can be
found at [cfssl_trust](https://github.com/cloudflare/cfssl_trust)
Specify PEM-encoded client certificate and key through '-cert' and
'-key' respectively. If key is specified, the bundle will be built
and verified with the key. Otherwise the bundle will be built
without a private key. Instead of file path, use '-' for reading
certificate PEM from stdin. It is also acceptable the certificate
file contains a (partial) certificate bundle.
Specify bundling flavor through '-flavor'. There are three flavors:
'optimal' to generate a bundle of shortest chain and most advanced
cryptographic algorithms, 'ubiquitous' to generate a bundle of most
widely acceptance across different browsers and OS platforms, and
'force' to find an acceptable bundle which is identical to the
content of the input certificate file.
Alternatively, the client certificate can be pulled directly from
a domain. It is also possible to connect to the remote address
through '-ip'.
```
cfssl bundle [-ca-bundle bundle] [-int-bundle bundle] \
[-metadata metadata_file] [-flavor bundle_flavor] \
-domain domain_name [-ip ip_address]
```
The bundle output form should follow the example
```json
{
"bundle": "CERT_BUNDLE_IN_PEM",
"crt": "LEAF_CERT_IN_PEM",
"crl_support": true,
"expires": "2015-12-31T23:59:59Z",
"hostnames": ["example.com"],
"issuer": "ISSUER CERT SUBJECT",
"key": "KEY_IN_PEM",
"key_size": 2048,
"key_type": "2048-bit RSA",
"ocsp": ["http://ocsp.example-ca.com"],
"ocsp_support": true,
"root": "ROOT_CA_CERT_IN_PEM",
"signature": "SHA1WithRSA",
"subject": "LEAF CERT SUBJECT",
"status": {
"rebundled": false,
"expiring_SKIs": [],
"untrusted_root_stores": [],
"messages": [],
"code": 0
}
}
```
#### Generating certificate signing request and private key
```
cfssl genkey csr.json
```
To generate a private key and corresponding certificate request, specify
the key request as a JSON file. This file should follow the form
```json
{
"hosts": [
"example.com",
"www.example.com"
],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "San Francisco",
"O": "Internet Widgets, Inc.",
"OU": "WWW",
"ST": "California"
}
]
}
```
#### Generating self-signed root CA certificate and private key
```
cfssl genkey -initca csr.json | cfssljson -bare ca
```
To generate a self-signed root CA certificate, specify the key request as
the JSON file in the same format as in 'genkey'. Three PEM-encoded entities
will appear in the output: the private key, the csr, and the self-signed
certificate.
#### Generating a remote-issued certificate and private key.
```
cfssl gencert -remote=remote_server [-hostname=comma,separated,hostnames] csr.json
```
This is calls genkey, but has a remote CFSSL server sign and issue
a certificate. You may use `-hostname` to override certificate SANs.
#### Generating a local-issued certificate and private key.
```
cfssl gencert -ca cert -ca-key key [-hostname=comma,separated,hostnames] csr.json
```
This is generates and issues a certificate and private key from a local CA
via a JSON request. You may use `-hostname` to override certificate SANs.
#### Updating a OCSP responses file with a newly issued certificate
```
cfssl ocspsign -ca cert -responder key -responder-key key -cert cert \
| cfssljson -bare -stdout >> responses
```
This will generate a OCSP response for the `cert` and add it to the
`responses` file. You can then pass `responses` to `ocspserve` to start a
OCSP server.
### Starting the API Server
CFSSL comes with an HTTP-based API server; the endpoints are
documented in `doc/api/intro.txt`. The server is started with the "serve"
command:
```
cfssl serve [-address address] [-ca cert] [-ca-bundle bundle] \
[-ca-key key] [-int-bundle bundle] [-int-dir dir] [-port port] \
[-metadata file] [-remote remote_host] [-config config] \
[-responder cert] [-responder-key key] [-db-config db-config]
```
Address and port default to "127.0.0.1:8888". The `-ca` and `-ca-key`
arguments should be the PEM-encoded certificate and private key to use
for signing; by default, they are "ca.pem" and "ca_key.pem". The
`-ca-bundle` and `-int-bundle` should be the certificate bundles used
for the root and intermediate certificate pools, respectively. These
default to "ca-bundle.crt" and "int-bundle." If the "remote" option is
provided, all signature operations will be forwarded to the remote CFSSL.
'-int-dir' specifies intermediates directory. '-metadata' is a file for
root certificate presence. The content of the file is a json dictionary
(k,v): each key k is SHA-1 digest of a root certificate while value v
is a list of key store filenames. '-config' specifies path to configuration
file. '-responder' and '-responder-key' are Certificate for OCSP responder
and private key for OCSP responder certificate, respectively.
The amount of logging can be controlled with the `-loglevel` option. This
comes *after* the serve command:
```
cfssl serve -loglevel 2
```
The levels are:
* 0. DEBUG
* 1. INFO (this is the default level)
* 2. WARNING
* 3. ERROR
* 4. CRITICAL
### The multirootca
The `cfssl` program can act as an online certificate authority, but it
only uses a single key. If multiple signing keys are needed, the
`multirootca` program can be used. It only provides the sign,
authsign, and info endpoints. The documentation contains instructions
for configuring and running the CA.
### The mkbundle Utility
`mkbundle` is used to build the root and intermediate bundles used in
verifying certificates. It can be installed with
```
go get -u github.com/cloudflare/cfssl/cmd/mkbundle
```
It takes a collection of certificates, checks for CRL revocation (OCSP
support is planned for the next release) and expired certificates, and
bundles them into one file. It takes directories of certificates and
certificate files (which may contain multiple certificates). For example,
if the directory `intermediates` contains a number of intermediate
certificates,
```
mkbundle -f int-bundle.crt intermediates
```
will check those certificates and combine valid ones into a single
`int-bundle.crt` file.
The `-f` flag specifies an output name; `-loglevel` specifies the verbosity
of the logging (using the same loglevels above), and `-nw` controls the
number of revocation-checking workers.
### The cfssljson Utility
Most of the output from `cfssl` is in JSON. The `cfssljson` will take
this output and split it out into separate key, certificate, CSR, and
bundle files as appropriate. The tool takes a single flag, `-f`, that
specifies the input file, and an argument that specifies the base name for
the files produced. If the input filename is "-" (which is the default),
`cfssljson` reads from standard input. It maps keys in the JSON file to
filenames in the following way:
* if there is a "cert" (or if not, if there's a "certificate") field, the
file "basename.pem" will be produced.
* if there is a "key" (or if not, if there's a "private_key") field, the
file "basename-key.pem" will be produced.
* if there is a "csr" (or if not, if there's a "certificate_request") field,
the file "basename.csr" will be produced.
* if there is a "bundle" field, the file "basename-bundle.pem" will
be produced.
* if there is a "ocspResponse" field, the file "basename-response.der" will
be produced.
Instead of saving to a file, you can pass `-stdout` to output the encoded
contents.
### Static Builds
By default, the web assets are accessed from disk, based on their
relative locations. If youre wishing to distribute a single,
statically-linked, cfssl binary, youll want to embed these resources
before building. This can by done with the
[go.rice](https://github.com/GeertJohan/go.rice) tool.
```
pushd cli/serve && rice embed-go && popd
```
Then building with `go build` will use the embedded resources.
### Using a PKCS#11 hardware token / HSM
For better security, you may want to store your private key in an HSM or
smartcard. The interface to both of these categories of device is described by
the PKCS#11 spec. If you need to do approximately one signing operation per
second or fewer, the Yubikey NEO and NEO-n are inexpensive smartcard options:
https://www.yubico.com/products/yubikey-hardware/yubikey-neo/. In general you
are looking for a product that supports PIV (personal identity verification). If
your signing needs are in the hundreds of signatures per second, you will need
to purchase an expensive HSM (in the thousands to many thousands of USD).
If you want to try out the PKCS#11 signing modes without a hardware token, you
can use the [SoftHSM](https://github.com/opendnssec/SoftHSMv1#softhsm)
implementation. Please note that using SoftHSM simply stores your private key in
a file on disk and does not increase security.
To get started with your PKCS#11 token you will need to initialize it with a
private key, PIN, and token label. The instructions to do this will be specific
to each hardware device, and you should follow the instructions provided by your
vendor. You will also need to find the path to your 'module', a shared object
file (.so). Having initialized your device, you can query it to check your token
label with:
pkcs11-tool --module <module path> --list-token-slots
You'll also want to check the label of the private key you imported (or
generated). Run the following command and look for a 'Private Key Object':
pkcs11-tool --module <module path> --pin <pin> \
--list-token-slots --login --list-objects
You now have all the information you need to use your PKCS#11 token with CFSSL.
CFSSL supports PKCS#11 for certificate signing and OCSP signing. To create a
Signer (for certificate signing), import `signer/universal` and call NewSigner
with a Root object containing the module, pin, token label and private label
from above, plus a path to your certificate. The structure of the Root object is
documented in universal.go.
Alternately, you can construct a pkcs11key.Key or pkcs11key.Pool yourself, and
pass it to ocsp.NewSigner (for OCSP) or local.NewSigner (for certificate
signing). This will be necessary, for example, if you are using a single-session
token like the Yubikey and need both OCSP signing and certificate signing at the
same time.
### Additional Documentation
Additional documentation can be found in the "doc/" directory:
* `api/intro.txt`: documents the API endpoints
* `bootstrap.txt`: a walkthrough from building the package to getting
up and running

231
vendor/github.com/cloudflare/cfssl/api/api.go generated vendored Normal file
View File

@@ -0,0 +1,231 @@
// Package api implements an HTTP-based API and server for CFSSL.
package api
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/cloudflare/cfssl/errors"
"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 methods.
type HTTPHandler struct {
Handler // CFSSL handler
Methods []string // The associated HTTP methods
}
// 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 {
w.Header().Set("Content-Type", "application/json")
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 cfssl,
// we rewrite the status code accordingly. If it is a
// cfssl 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
var match bool
// Throw 405 when requested with an unsupported verb.
for _, m := range h.Methods {
if m == r.Method {
match = true
}
}
if match {
err = h.Handle(w, r)
} else {
err = errors.NewMethodNotAllowed(r.Method)
}
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.
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.
func NewSuccessResponse(result interface{}) Response {
return Response{
Success: true,
Result: result,
Errors: []ResponseMessage{},
Messages: []ResponseMessage{},
}
}
// NewSuccessResponseWithMessage is a shortcut for creating new successul API
// responses that includes a message.
func NewSuccessResponseWithMessage(result interface{}, message string, code int) Response {
return Response{
Success: true,
Result: result,
Errors: []ResponseMessage{},
Messages: []ResponseMessage{{code, message}},
}
}
// 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
}
// SendResponseWithMessage builds a response from the result and the
// provided message, sets the JSON header, and writes to the
// http.ResponseWriter.
func SendResponseWithMessage(w http.ResponseWriter, result interface{}, message string, code int) error {
response := NewSuccessResponseWithMessage(result, message, code)
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
err := enc.Encode(response)
return err
}

220
vendor/github.com/cloudflare/cfssl/api/api_test.go generated vendored Normal file
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{
{"compliment"},
{"critique"},
})
if err != nil {
return err
}
return SendResponse(w, ty)
}
func cleverHandle(w http.ResponseWriter, r *http.Request) error {
_, matched, err := ProcessRequestFirstMatchOf(r, [][]string{
{"compliment"},
{"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), Methods: []string{"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, _ = 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, _ = post(t, obj, ts)
if resp.StatusCode != http.StatusBadRequest {
t.Errorf("Test expected 400, have %d", resp.StatusCode)
}
// reject GET
resp, _ = 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), Methods: []string{"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"
_, 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, _ = post(t, obj, ts)
if resp.StatusCode != http.StatusBadRequest {
t.Errorf("Test expected 400, have %d", resp.StatusCode)
}
// reject GET
resp, _ = get(t, ts)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("Test expected 405, have %d", resp.StatusCode)
}
}

View File

@@ -0,0 +1,91 @@
// Package bundle implements the HTTP handler for the bundle command.
package bundle
import (
"net/http"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/bundler"
"github.com/cloudflare/cfssl/errors"
"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, Methods: []string{"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{
{"certificate"},
{"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.BundleFromPEMorDER([]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,213 @@
package bundle
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"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,
},
// 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 != 0 && 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,193 @@
package certadd
import (
"bytes"
"encoding/hex"
"encoding/json"
"io/ioutil"
"math/big"
"net/http"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/certdb"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/ocsp"
"encoding/base64"
stdocsp "golang.org/x/crypto/ocsp"
)
// This is patterned on
// https://github.com/cloudflare/cfssl/blob/master/api/revoke/revoke.go. This
// file defines an HTTP endpoint handler that accepts certificates and
// inserts them into a certdb, optionally also creating an OCSP
// response for them. If so, it will also return the OCSP response as
// a base64 encoded string.
// A Handler accepts new SSL certificates and inserts them into the
// certdb, creating an appropriate OCSP response for them.
type Handler struct {
dbAccessor certdb.Accessor
signer ocsp.Signer
}
// NewHandler creates a new Handler from a certdb.Accessor and ocsp.Signer
func NewHandler(dbAccessor certdb.Accessor, signer ocsp.Signer) http.Handler {
return &api.HTTPHandler{
Handler: &Handler{
dbAccessor: dbAccessor,
signer: signer,
},
Methods: []string{"POST"},
}
}
// AddRequest describes a request from a client to insert a
// certificate into the database.
type AddRequest struct {
Serial string `json:"serial_number"`
AKI string `json:"authority_key_identifier"`
CALabel string `json:"ca_label"`
Status string `json:"status"`
Reason int `json:"reason"`
Expiry time.Time `json:"expiry"`
RevokedAt time.Time `json:"revoked_at"`
PEM string `json:"pem"`
}
// Map of valid reason codes
var validReasons = map[int]bool{
stdocsp.Unspecified: true,
stdocsp.KeyCompromise: true,
stdocsp.CACompromise: true,
stdocsp.AffiliationChanged: true,
stdocsp.Superseded: true,
stdocsp.CessationOfOperation: true,
stdocsp.CertificateHold: true,
stdocsp.RemoveFromCRL: true,
stdocsp.PrivilegeWithdrawn: true,
stdocsp.AACompromise: true,
}
// Handle handles HTTP requests to add certificates
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
r.Body.Close()
var req AddRequest
err = json.Unmarshal(body, &req)
if err != nil {
return errors.NewBadRequestString("Unable to parse certificate addition request")
}
if len(req.Serial) == 0 {
return errors.NewBadRequestString("Serial number is required but not provided")
}
if len(req.AKI) == 0 {
return errors.NewBadRequestString("Authority key identifier is required but not provided")
}
if _, present := ocsp.StatusCode[req.Status]; !present {
return errors.NewBadRequestString("Invalid certificate status")
}
if ocsp.StatusCode[req.Status] == stdocsp.Revoked {
if req.RevokedAt == (time.Time{}) {
return errors.NewBadRequestString("Revoked certificate should specify when it was revoked")
}
if _, present := validReasons[req.Reason]; !present {
return errors.NewBadRequestString("Invalid certificate status reason code")
}
}
if len(req.PEM) == 0 {
return errors.NewBadRequestString("The provided certificate is empty")
}
// Parse the certificate and validate that it matches
cert, err := helpers.ParseCertificatePEM([]byte(req.PEM))
if err != nil {
return errors.NewBadRequestString("Unable to parse PEM encoded certificates")
}
serialBigInt := new(big.Int)
if _, success := serialBigInt.SetString(req.Serial, 16); !success {
return errors.NewBadRequestString("Unable to parse serial key of request")
}
if serialBigInt.Cmp(cert.SerialNumber) != 0 {
return errors.NewBadRequestString("Serial key of request and certificate do not match")
}
aki, err := hex.DecodeString(req.AKI)
if err != nil {
return errors.NewBadRequestString("Unable to decode authority key identifier")
}
if !bytes.Equal(aki, cert.AuthorityKeyId) {
return errors.NewBadRequestString("Authority key identifier of request and certificate do not match")
}
cr := certdb.CertificateRecord{
Serial: req.Serial,
AKI: req.AKI,
CALabel: req.CALabel,
Status: req.Status,
Reason: req.Reason,
Expiry: req.Expiry,
RevokedAt: req.RevokedAt,
PEM: req.PEM,
}
err = h.dbAccessor.InsertCertificate(cr)
if err != nil {
return err
}
result := map[string]string{}
if h.signer != nil {
// Now create an appropriate OCSP response
sr := ocsp.SignRequest{
Certificate: cert,
Status: req.Status,
Reason: req.Reason,
RevokedAt: req.RevokedAt,
}
ocspResponse, err := h.signer.Sign(sr)
if err != nil {
return err
}
// We parse the OCSP repsonse in order to get the next
// update time/expiry time
ocspParsed, err := stdocsp.ParseResponse(ocspResponse, nil)
if err != nil {
return err
}
result["ocsp_response"] = base64.StdEncoding.EncodeToString(ocspResponse)
ocspRecord := certdb.OCSPRecord{
Serial: req.Serial,
AKI: req.AKI,
Body: string(ocspResponse),
Expiry: ocspParsed.NextUpdate,
}
if err = h.dbAccessor.InsertOCSP(ocspRecord); err != nil {
return err
}
}
return api.SendResponse(w, result)
}

View File

@@ -0,0 +1,491 @@
package certadd
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/json"
"encoding/pem"
"io/ioutil"
"math/big"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/cloudflare/cfssl/certdb"
"github.com/cloudflare/cfssl/certdb/sql"
"github.com/cloudflare/cfssl/certdb/testdb"
"github.com/cloudflare/cfssl/ocsp"
"encoding/base64"
stdocsp "golang.org/x/crypto/ocsp"
)
func prepDB() (certdb.Accessor, error) {
db := testdb.SQLiteDB("../../certdb/testdb/certstore_development.db")
dbAccessor := sql.NewAccessor(db)
return dbAccessor, nil
}
func makeRequest(t *testing.T, dbAccessor certdb.Accessor, signer ocsp.Signer, req map[string]interface{}) (resp *http.Response, body []byte) {
ts := httptest.NewServer(NewHandler(dbAccessor, signer))
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
}
func makeCertificate() (serialNumber *big.Int, cert *x509.Certificate, pemBytes []byte, signer ocsp.Signer, err error) {
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return
}
serialNumberRange := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err = rand.Int(rand.Reader, serialNumberRange)
if err != nil {
return
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Cornell CS 5152"},
},
AuthorityKeyId: []byte{42, 42, 42, 42},
}
cert = &template
issuerSerial, err := rand.Int(rand.Reader, serialNumberRange)
if err != nil {
return
}
responderSerial, err := rand.Int(rand.Reader, serialNumberRange)
if err != nil {
return
}
// Generate a CA certificate
issuerTemplate := x509.Certificate{
SerialNumber: issuerSerial,
Subject: pkix.Name{
Organization: []string{"Cornell CS 5152"},
},
AuthorityKeyId: []byte{42, 42, 42, 42},
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
IsCA: true,
BasicConstraintsValid: true,
}
issuerBytes, err := x509.CreateCertificate(rand.Reader, &issuerTemplate, &issuerTemplate, &privKey.PublicKey, privKey)
if err != nil {
return
}
issuer, err := x509.ParseCertificate(issuerBytes)
if err != nil {
return
}
responderTemplate := x509.Certificate{
SerialNumber: responderSerial,
Subject: pkix.Name{
Organization: []string{"Cornell CS 5152 Responder"},
},
AuthorityKeyId: []byte{42, 42, 42, 43},
}
responderBytes, err := x509.CreateCertificate(rand.Reader, &responderTemplate, &responderTemplate, &privKey.PublicKey, privKey)
if err != nil {
return
}
responder, err := x509.ParseCertificate(responderBytes)
if err != nil {
return
}
signer, err = ocsp.NewSigner(issuer, responder, privKey, time.Hour)
if err != nil {
return
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, issuer, &privKey.PublicKey, privKey)
if err != nil {
return
}
pemBytes = pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: derBytes,
})
return
}
func TestInsertValidCertificate(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, cert, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": string(pemBytes),
})
if resp.StatusCode != http.StatusOK {
t.Fatal("Expected HTTP OK, got", resp.StatusCode, string(body))
}
var response map[string]interface{}
if err = json.Unmarshal(body, &response); err != nil {
t.Fatal("Could not parse response: ", err)
}
responseResult := response["result"].(map[string]interface{})
encodedOcsp := responseResult["ocsp_response"].(string)
rawOcsp, err := base64.StdEncoding.DecodeString(encodedOcsp)
if err != nil {
t.Fatal("Could not base64 decode response: ", err)
}
returnedOcsp, err := stdocsp.ParseResponse(rawOcsp, nil)
if err != nil {
t.Fatal("Could not parse returned OCSP response", err)
}
ocsps, err := dbAccessor.GetOCSP(serialNumber.Text(16), hex.EncodeToString(cert.AuthorityKeyId))
if err != nil {
t.Fatal(err)
}
if len(ocsps) != 1 {
t.Fatal("Expected 1 OCSP record to be inserted, but found ", len(ocsps))
}
parsedOcsp, err := stdocsp.ParseResponse([]byte(ocsps[0].Body), nil)
if err != nil {
t.Fatal(err)
}
if parsedOcsp.SerialNumber.Cmp(returnedOcsp.SerialNumber) != 0 {
t.Fatal("The returned and inserted OCSP response have different serial numbers: got ", returnedOcsp.SerialNumber, " but decoded ", parsedOcsp.SerialNumber)
}
if parsedOcsp.SerialNumber.Cmp(serialNumber) != 0 {
t.Fatal("Got the wrong serial number: expected", serialNumber, "but got", parsedOcsp.SerialNumber)
}
if parsedOcsp.Status != stdocsp.Good {
t.Fatal("Expected OCSP response status to be ", stdocsp.Good,
" but found ", parsedOcsp.Status)
}
}
func TestInsertMissingSerial(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
_, cert, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": string(pemBytes),
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}
func TestInsertMissingAKI(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, _, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"status": "good",
"pem": string(pemBytes),
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}
func TestInsertMissingPEM(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, cert, _, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}
func TestInsertInvalidSerial(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
_, cert, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": "this is not a serial number",
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": string(pemBytes),
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}
func TestInsertInvalidAKI(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, _, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"authority_key_identifier": "this is not an AKI",
"status": "good",
"pem": string(pemBytes),
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request, got", resp.StatusCode, string(body))
}
}
func TestInsertInvalidStatus(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, cert, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "invalid",
"pem": string(pemBytes),
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}
func TestInsertInvalidPEM(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, cert, _, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": "this is not a PEM certificate",
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request, got", resp.StatusCode, string(body))
}
}
func TestInsertWrongSerial(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
_, cert, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": big.NewInt(1).Text(16),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": string(pemBytes),
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}
func TestInsertWrongAKI(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, _, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"authority_key_identifier": hex.EncodeToString([]byte{7, 7}),
"status": "good",
"pem": string(pemBytes),
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}
func TestInsertRevokedCertificate(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, cert, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "revoked",
"pem": string(pemBytes),
"revoked_at": time.Now(),
})
if resp.StatusCode != http.StatusOK {
t.Fatal("Expected HTTP OK", resp.StatusCode, string(body))
}
ocsps, err := dbAccessor.GetOCSP(serialNumber.Text(16), hex.EncodeToString(cert.AuthorityKeyId))
if err != nil {
t.Fatal(err)
}
if len(ocsps) != 1 {
t.Fatal("Expected 1 OCSP record to be inserted, but found ", len(ocsps))
}
response, err := stdocsp.ParseResponse([]byte(ocsps[0].Body), nil)
if err != nil {
t.Fatal(err)
}
if response.Status != stdocsp.Revoked {
t.Fatal("Expected OCSP response status to be ", stdocsp.Revoked,
" but found ", response.Status)
}
}
func TestInsertRevokedCertificateWithoutTime(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
serialNumber, cert, pemBytes, signer, err := makeCertificate()
if err != nil {
t.Fatal(err)
}
resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "revoked",
"pem": string(pemBytes),
// Omit RevokedAt
})
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}

View File

@@ -0,0 +1,50 @@
// Package certinfo implements the HTTP handler for the certinfo command.
package certinfo
import (
"net/http"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/certinfo"
"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{}
// NewHandler creates a new bundler that uses the root bundle and
// intermediate bundle in the trust chain.
func NewHandler() http.Handler {
return api.HTTPHandler{Handler: new(Handler), Methods: []string{"POST"}}
}
// Handle implements an http.Handler interface for the bundle handler.
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) (err error) {
blob, matched, err := api.ProcessRequestFirstMatchOf(r,
[][]string{
{"certificate"},
{"domain"},
})
if err != nil {
log.Warningf("invalid request: %v", err)
return err
}
var cert *certinfo.Certificate
switch matched[0] {
case "domain":
if cert, err = certinfo.ParseCertificateDomain(blob["domain"]); err != nil {
log.Warningf("couldn't parse remote certificate: %v", err)
return err
}
case "certificate":
if cert, err = certinfo.ParseCertificatePEM([]byte(blob["certificate"])); err != nil {
log.Warningf("bad PEM certifcate: %v", err)
return err
}
}
return api.SendResponse(w, cert)
}

6
vendor/github.com/cloudflare/cfssl/api/client/api.go generated vendored Normal file
View File

@@ -0,0 +1,6 @@
package client
// SignResult is the result of signing a CSR.
type SignResult struct {
Certificate []byte `json:"certificate"`
}

346
vendor/github.com/cloudflare/cfssl/api/client/client.go generated vendored Normal file
View File

@@ -0,0 +1,346 @@
// Package client implements the a Go client for CFSSL API commands.
package client
import (
"bytes"
"crypto/tls"
"encoding/json"
stderr "errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/auth"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/info"
"github.com/cloudflare/cfssl/log"
)
// A server points to a single remote CFSSL instance.
type server struct {
URL string
TLSConfig *tls.Config
reqModifier func(*http.Request, []byte)
RequestTimeout time.Duration
}
// A Remote points to at least one (but possibly multiple) remote
// CFSSL instances. It must be able to perform a authenticated and
// unauthenticated certificate signing requests, return information
// about the CA on the other end, and return a list of the hosts that
// are used by the remote.
type Remote interface {
AuthSign(req, id []byte, provider auth.Provider) ([]byte, error)
Sign(jsonData []byte) ([]byte, error)
Info(jsonData []byte) (*info.Resp, error)
Hosts() []string
SetReqModifier(func(*http.Request, []byte))
SetRequestTimeout(d time.Duration)
}
// NewServer sets up a new server target. The address should be of
// The format [protocol:]name[:port] of the remote CFSSL instance.
// If no protocol is given http is default. If no port
// is specified, the CFSSL default port (8888) is used. If the name is
// a comma-separated list of hosts, an ordered group will be returned.
func NewServer(addr string) Remote {
return NewServerTLS(addr, nil)
}
// NewServerTLS is the TLS version of NewServer
func NewServerTLS(addr string, tlsConfig *tls.Config) Remote {
addrs := strings.Split(addr, ",")
var remote Remote
if len(addrs) > 1 {
remote, _ = NewGroup(addrs, tlsConfig, StrategyOrderedList)
} else {
u, err := normalizeURL(addrs[0])
if err != nil {
log.Errorf("bad url: %v", err)
return nil
}
srv := newServer(u, tlsConfig)
if srv != nil {
remote = srv
}
}
return remote
}
func (srv *server) Hosts() []string {
return []string{srv.URL}
}
func (srv *server) SetReqModifier(mod func(*http.Request, []byte)) {
srv.reqModifier = mod
}
func (srv *server) SetRequestTimeout(timeout time.Duration) {
srv.RequestTimeout = timeout
}
func newServer(u *url.URL, tlsConfig *tls.Config) *server {
URL := u.String()
return &server{
URL: URL,
TLSConfig: tlsConfig,
}
}
func (srv *server) getURL(endpoint string) string {
return fmt.Sprintf("%s/api/v1/cfssl/%s", srv.URL, endpoint)
}
func (srv *server) createTLSTransport() (transport *http.Transport) {
// Setup HTTPS client
tlsConfig := srv.TLSConfig
tlsConfig.BuildNameToCertificate()
return &http.Transport{TLSClientConfig: tlsConfig}
}
// post connects to the remote server and returns a Response struct
func (srv *server) post(url string, jsonData []byte) (*api.Response, error) {
var resp *http.Response
var err error
client := &http.Client{}
if srv.TLSConfig != nil {
client.Transport = srv.createTLSTransport()
}
if srv.RequestTimeout != 0 {
client.Timeout = srv.RequestTimeout
}
req, err := http.NewRequest("POST", url, bytes.NewReader(jsonData))
if err != nil {
err = fmt.Errorf("failed POST to %s: %v", url, err)
return nil, errors.Wrap(errors.APIClientError, errors.ClientHTTPError, err)
}
req.Close = true
req.Header.Set("content-type", "application/json")
if srv.reqModifier != nil {
srv.reqModifier(req, jsonData)
}
resp, err = client.Do(req)
if err != nil {
err = fmt.Errorf("failed POST to %s: %v", url, err)
return nil, errors.Wrap(errors.APIClientError, errors.ClientHTTPError, err)
}
defer req.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(errors.APIClientError, errors.IOError, err)
}
if resp.StatusCode != http.StatusOK {
log.Errorf("http error with %s", url)
return nil, errors.Wrap(errors.APIClientError, errors.ClientHTTPError, stderr.New(string(body)))
}
var response api.Response
err = json.Unmarshal(body, &response)
if err != nil {
log.Debug("Unable to parse response body:", string(body))
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.request(jsonData, "sign")
}
// Info sends an info request to the remote CFSSL server, receiving a
// response or an error in response.
// It takes the serialized JSON request to send.
func (srv *server) Info(jsonData []byte) (*info.Resp, error) {
res, err := srv.getResultMap(jsonData, "info")
if err != nil {
return nil, err
}
info := new(info.Resp)
if val, ok := res["certificate"]; ok {
info.Certificate = val.(string)
}
var usages []interface{}
if val, ok := res["usages"]; ok && val != nil {
usages = val.([]interface{})
}
if val, ok := res["expiry"]; ok && val != nil {
info.ExpiryString = val.(string)
}
info.Usage = make([]string, len(usages))
for i, s := range usages {
info.Usage[i] = s.(string)
}
return info, nil
}
func (srv *server) getResultMap(jsonData []byte, target string) (result map[string]interface{}, err error) {
url := srv.getURL(target)
response, err := srv.post(url, jsonData)
if err != nil {
return
}
result, ok := response.Result.(map[string]interface{})
if !ok {
err = errors.Wrap(errors.APIClientError, errors.ClientHTTPError, stderr.New("response is formatted improperly"))
return
}
return
}
// request performs the common logic for Sign and Info, performing the actual
// request and returning the resultant certificate.
func (srv *server) request(jsonData []byte, target string) ([]byte, error) {
result, err := srv.getResultMap(jsonData, target)
if err != nil {
return nil, err
}
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."))
}
// AuthRemote acts as a Remote with a default Provider for AuthSign.
type AuthRemote struct {
Remote
provider auth.Provider
}
// NewAuthServer sets up a new auth server target with an addr
// in the same format at NewServer and a default authentication provider to
// use for Sign requests.
func NewAuthServer(addr string, tlsConfig *tls.Config, provider auth.Provider) *AuthRemote {
return &AuthRemote{
Remote: NewServerTLS(addr, tlsConfig),
provider: provider,
}
}
// Sign is overloaded to perform an AuthSign request using the default auth provider.
func (ar *AuthRemote) Sign(req []byte) ([]byte, error) {
return ar.AuthSign(req, nil, ar.provider)
}
// nomalizeURL checks for http/https protocol, appends "http" as default protocol if not defiend in url
func normalizeURL(addr string) (*url.URL, error) {
addr = strings.TrimSpace(addr)
u, err := url.Parse(addr)
if err != nil {
return nil, err
}
if u.Opaque != "" {
u.Host = net.JoinHostPort(u.Scheme, u.Opaque)
u.Opaque = ""
} else if u.Path != "" && !strings.Contains(u.Path, ":") {
u.Host = net.JoinHostPort(u.Path, "8888")
u.Path = ""
} else if u.Scheme == "" {
u.Host = u.Path
u.Path = ""
}
if u.Scheme != "https" {
u.Scheme = "http"
}
_, port, err := net.SplitHostPort(u.Host)
if err != nil {
_, port, err = net.SplitHostPort(u.Host + ":8888")
if err != nil {
return nil, err
}
}
if port != "" {
_, err = strconv.Atoi(port)
if err != nil {
return nil, err
}
}
return u, nil
}

View File

@@ -0,0 +1,203 @@
package client
import (
"crypto/tls"
"github.com/cloudflare/cfssl/auth"
"github.com/cloudflare/cfssl/helpers"
"net"
"strings"
"testing"
)
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)
}
s = NewServer("http://127.0.0.1:8888")
hosts := s.Hosts()
if len(hosts) != 1 || hosts[0] != "http://127.0.0.1:8888" {
t.Fatalf("expected [http://127.0.0.1:8888], but have %v", hosts)
}
s = NewServer("http://1.1.1.1:9999")
hosts = s.Hosts()
if len(hosts) != 1 || hosts[0] != "http://1.1.1.1:9999" {
t.Fatalf("expected [http://1.1.1.1:9999], but have %v", hosts)
}
s = NewServer("https://1.1.1.1:8080")
hosts = s.Hosts()
if len(hosts) != 1 || hosts[0] != "https://1.1.1.1:8080" {
t.Fatalf("expected [https://1.1.1.1:8080], but have %v", hosts)
}
}
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(".X")
testProvider, _ = auth.New(testKey, nil)
testRequest := []byte(`testing 1 2 3`)
as, err := s.AuthSign(testRequest, testAD, testProvider)
if as != nil || err == nil {
t.Fatal("expected error with auth sign function")
}
}
func TestDefaultAuthSign(t *testing.T) {
testProvider, _ = auth.New(testKey, nil)
s := NewAuthServer(".X", nil, testProvider)
testRequest := []byte(`testing 1 2 3`)
as, err := s.Sign(testRequest)
if as != nil || err == nil {
t.Fatal("expected error with auth sign function")
}
}
func TestSign(t *testing.T) {
s := NewServer(".X")
sign, err := s.Sign([]byte{5, 5, 5, 5})
if sign != nil || err == nil {
t.Fatalf("expected error with sign function")
}
}
func TestNewMutualTLSServer(t *testing.T) {
cert, _ := helpers.LoadClientCertificate("../../helpers/testdata/ca.pem", "../../helpers/testdata/ca_key.pem")
s := NewServerTLS("https://nohost:8888", helpers.CreateTLSConfig(nil, cert))
if s == nil {
t.Fatalf("fatal error, empty server")
}
_, err := s.Sign([]byte{5, 5, 5, 5})
if err == nil {
t.Fatalf("expected error with sign function")
}
if !strings.Contains(err.Error(), "Post https://nohost:8888/api/v1/cfssl/sign: dial tcp: lookup nohost") {
t.Fatalf("no error message %v", err)
}
}
func TestNewServerGroup(t *testing.T) {
s := NewServer("cfssl1.local:8888, cfssl2.local:8888, http://cfssl3.local:8888, http://cfssl4.local:8888")
ogl, ok := s.(*orderedListGroup)
if !ok {
t.Fatalf("expected NewServer to return an ordered group list with a list of servers, instead got a %T = %+v", ogl, ogl)
}
if len(ogl.remotes) != 4 {
t.Fatalf("expected the remote to have four servers, but it has %d", len(ogl.remotes))
}
hosts := ogl.Hosts()
if len(hosts) != 4 {
t.Fatalf("expected 2 hosts in the group, but have %d", len(hosts))
}
if hosts[0] != "http://cfssl1.local:8888" {
t.Fatalf("expected to see http://cfssl1.local:8888, but saw %s",
hosts[0])
}
if hosts[1] != "http://cfssl2.local:8888" {
t.Fatalf("expected to see http://cfssl2.local:8888, but saw %s",
hosts[1])
}
if hosts[2] != "http://cfssl3.local:8888" {
t.Fatalf("expected to see http://cfssl1.local:8888, but saw %s",
hosts[2])
}
if hosts[3] != "http://cfssl4.local:8888" {
t.Fatalf("expected to see http://cfssl2.local:8888, but saw %s",
hosts[3])
}
}
func TestNewTLSServerGroup(t *testing.T) {
NewTLSServerGroup(t, nil)
}
func TestNewMutualTLSServerGroup(t *testing.T) {
cert, _ := helpers.LoadClientCertificate("../../helpers/testdata/ca.pem", "../../helpers/testdata/ca_key.pem")
NewTLSServerGroup(t, cert)
}
func NewTLSServerGroup(t *testing.T, cert *tls.Certificate) {
s := NewServerTLS("https://cfssl1.local:8888, https://cfssl2.local:8888", helpers.CreateTLSConfig(nil, cert))
ogl, ok := s.(*orderedListGroup)
if !ok {
t.Fatalf("expected NewServer to return an ordered group list with a list of servers, instead got a %T = %+v", ogl, ogl)
}
if len(ogl.remotes) != 2 {
t.Fatalf("expected the remote to have two servers, but it has %d", len(ogl.remotes))
}
hosts := ogl.Hosts()
if len(hosts) != 2 {
t.Fatalf("expected 2 hosts in the group, but have %d", len(hosts))
}
if hosts[0] != "https://cfssl1.local:8888" {
t.Fatalf("expected to see https://cfssl1.local:8888, but saw %s",
hosts[0])
}
if hosts[1] != "https://cfssl2.local:8888" {
t.Fatalf("expected to see https://cfssl2.local:8888, but saw %s",
hosts[1])
}
}
func TestNewOGLGroup(t *testing.T) {
strategy := StrategyFromString("ordered_list")
if strategy == StrategyInvalid {
t.Fatal("expected StrategyOrderedList as selected strategy but have StrategyInvalid")
}
if strategy != StrategyOrderedList {
t.Fatalf("expected StrategyOrderedList (%d) but have %d", StrategyOrderedList, strategy)
}
rem, err := NewGroup([]string{"ca1.local,", "ca2.local"}, nil, strategy)
if err != nil {
t.Fatalf("%v", err)
}
ogl, ok := rem.(*orderedListGroup)
if !ok {
t.Fatalf("expected to get an orderedListGroup but got %T", rem)
}
if len(ogl.remotes) != 2 {
t.Fatalf("expected two remotes in the ordered group list but have %d", len(ogl.remotes))
}
}

125
vendor/github.com/cloudflare/cfssl/api/client/group.go generated vendored Normal file
View File

@@ -0,0 +1,125 @@
package client
import (
"crypto/tls"
"errors"
"net/http"
"strings"
"time"
"github.com/cloudflare/cfssl/auth"
"github.com/cloudflare/cfssl/info"
)
// Strategy is the means by which the server to use as a remote should
// be selected.
type Strategy int
const (
// StrategyInvalid indicates any strategy that is unsupported
// or returned when no strategy is applicable.
StrategyInvalid = iota
// StrategyOrderedList is a sequential list of servers: if the
// first server cannot be reached, the next is used. The
// client will proceed in this manner until the list of
// servers is exhausted, and then an error is returned.
StrategyOrderedList
)
var strategyStrings = map[string]Strategy{
"ordered_list": StrategyOrderedList,
}
// StrategyFromString takes a string describing a
func StrategyFromString(s string) Strategy {
s = strings.TrimSpace(strings.ToLower(s))
strategy, ok := strategyStrings[s]
if !ok {
return StrategyInvalid
}
return strategy
}
// NewGroup will use the collection of remotes specified with the
// given strategy.
func NewGroup(remotes []string, tlsConfig *tls.Config, strategy Strategy) (Remote, error) {
var servers = make([]*server, len(remotes))
for i := range remotes {
u, err := normalizeURL(remotes[i])
if err != nil {
return nil, err
}
servers[i] = newServer(u, tlsConfig)
}
switch strategy {
case StrategyOrderedList:
return newOrdererdListGroup(servers)
default:
return nil, errors.New("unrecognised strategy")
}
}
type orderedListGroup struct {
remotes []*server
}
func (g *orderedListGroup) Hosts() []string {
var hosts = make([]string, 0, len(g.remotes))
for _, srv := range g.remotes {
srvHosts := srv.Hosts()
hosts = append(hosts, srvHosts[0])
}
return hosts
}
func (g *orderedListGroup) SetRequestTimeout(timeout time.Duration) {
for _, srv := range g.remotes {
srv.SetRequestTimeout(timeout)
}
}
func newOrdererdListGroup(remotes []*server) (Remote, error) {
return &orderedListGroup{
remotes: remotes,
}, nil
}
func (g *orderedListGroup) AuthSign(req, id []byte, provider auth.Provider) (resp []byte, err error) {
for i := range g.remotes {
resp, err = g.remotes[i].AuthSign(req, id, provider)
if err == nil {
return resp, nil
}
}
return nil, err
}
func (g *orderedListGroup) Sign(jsonData []byte) (resp []byte, err error) {
for i := range g.remotes {
resp, err = g.remotes[i].Sign(jsonData)
if err == nil {
return resp, nil
}
}
return nil, err
}
func (g *orderedListGroup) Info(jsonData []byte) (resp *info.Resp, err error) {
for i := range g.remotes {
resp, err = g.remotes[i].Info(jsonData)
if err == nil {
return resp, nil
}
}
return nil, err
}
// SetReqModifier does nothing because there is no request modifier for group
func (g *orderedListGroup) SetReqModifier(mod func(*http.Request, []byte)) {
// noop
}

93
vendor/github.com/cloudflare/cfssl/api/crl/crl.go generated vendored Normal file
View File

@@ -0,0 +1,93 @@
// Package crl implements the HTTP handler for the crl command.
package crl
import (
"crypto"
"crypto/x509"
"net/http"
"os"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/certdb"
"github.com/cloudflare/cfssl/crl"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/log"
)
// A Handler accepts requests with a serial number parameter
// and revokes
type Handler struct {
dbAccessor certdb.Accessor
ca *x509.Certificate
key crypto.Signer
}
// NewHandler returns a new http.Handler that handles a revoke request.
func NewHandler(dbAccessor certdb.Accessor, caPath string, caKeyPath string) (http.Handler, error) {
ca, err := helpers.ReadBytes(caPath)
if err != nil {
return nil, err
}
caKey, err := helpers.ReadBytes(caKeyPath)
if err != nil {
return nil, errors.Wrap(errors.PrivateKeyError, errors.ReadFailed, err)
}
// Parse the PEM encoded certificate
issuerCert, err := helpers.ParseCertificatePEM(ca)
if err != nil {
return nil, err
}
strPassword := os.Getenv("CFSSL_CA_PK_PASSWORD")
password := []byte(strPassword)
if strPassword == "" {
password = nil
}
// Parse the key given
key, err := helpers.ParsePrivateKeyPEMWithPassword(caKey, password)
if err != nil {
log.Debug("malformed private key %v", err)
return nil, err
}
return &api.HTTPHandler{
Handler: &Handler{
dbAccessor: dbAccessor,
ca: issuerCert,
key: key,
},
Methods: []string{"GET"},
}, nil
}
// Handle responds to revocation requests. It attempts to revoke
// a certificate with a given serial number
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
var newExpiryTime = 7 * helpers.OneDay
certs, err := h.dbAccessor.GetRevokedAndUnexpiredCertificates()
if err != nil {
return err
}
queryExpiryTime := r.URL.Query().Get("expiry")
if queryExpiryTime != "" {
log.Infof("requested expiry time of %s", queryExpiryTime)
newExpiryTime, err = time.ParseDuration(queryExpiryTime)
if err != nil {
return err
}
}
result, err := crl.NewCRLFromDB(certs, h.ca, h.key, newExpiryTime)
if err != nil {
return err
}
return api.SendResponse(w, result)
}

149
vendor/github.com/cloudflare/cfssl/api/crl/crl_test.go generated vendored Normal file
View File

@@ -0,0 +1,149 @@
package crl
import (
"crypto/x509"
"encoding/base64"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/certdb"
"github.com/cloudflare/cfssl/certdb/sql"
"github.com/cloudflare/cfssl/certdb/testdb"
"github.com/cloudflare/cfssl/helpers"
)
const (
fakeAKI = "fake aki"
testCaFile = "../testdata/ca.pem"
testCaKeyFile = "../testdata/ca_key.pem"
)
func prepDB() (certdb.Accessor, error) {
db := testdb.SQLiteDB("../../certdb/testdb/certstore_development.db")
expirationTime := time.Now().AddDate(1, 0, 0)
var cert = certdb.CertificateRecord{
Serial: "1",
AKI: fakeAKI,
Expiry: expirationTime,
PEM: "revoked cert",
Status: "revoked",
RevokedAt: time.Now(),
Reason: 4,
}
dbAccessor := sql.NewAccessor(db)
err := dbAccessor.InsertCertificate(cert)
if err != nil {
return nil, err
}
return dbAccessor, nil
}
func testGetCRL(t *testing.T, dbAccessor certdb.Accessor, expiry string) (resp *http.Response, body []byte) {
handler, err := NewHandler(dbAccessor, testCaFile, testCaKeyFile)
if err != nil {
t.Fatal(err)
}
ts := httptest.NewServer(handler)
defer ts.Close()
if expiry != "" {
resp, err = http.Get(ts.URL + "?expiry=" + expiry)
} else {
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 TestCRLGeneration(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
resp, body := testGetCRL(t, dbAccessor, "")
if resp.StatusCode != http.StatusOK {
t.Fatal("unexpected HTTP status code; expected OK", string(body))
}
message := new(api.Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
crlBytes := message.Result.(string)
crlBytesDER, err := base64.StdEncoding.DecodeString(crlBytes)
if err != nil {
t.Fatal("failed to decode certificate ", err)
}
parsedCrl, err := x509.ParseCRL(crlBytesDER)
if err != nil {
t.Fatal("failed to get certificate ", err)
}
if parsedCrl.HasExpired(time.Now().Add(5 * helpers.OneDay)) {
t.Fatal("the request will expire after 5 days, this shouldn't happen")
}
certs := parsedCrl.TBSCertList.RevokedCertificates
if len(certs) != 1 {
t.Fatal("failed to get one certificate")
}
cert := certs[0]
if cert.SerialNumber.String() != "1" {
t.Fatal("cert was not correctly inserted in CRL, serial was ", cert.SerialNumber)
}
}
func TestCRLGenerationWithExpiry(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
resp, body := testGetCRL(t, dbAccessor, "119h")
if resp.StatusCode != http.StatusOK {
t.Fatal("unexpected HTTP status code; expected OK", string(body))
}
message := new(api.Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
crlBytes := message.Result.(string)
crlBytesDER, err := base64.StdEncoding.DecodeString(crlBytes)
if err != nil {
t.Fatal("failed to decode certificate ", err)
}
parsedCrl, err := x509.ParseCRL(crlBytesDER)
if err != nil {
t.Fatal("failed to get certificate ", err)
}
if !parsedCrl.HasExpired(time.Now().Add(5 * helpers.OneDay)) {
t.Fatal("the request should have expired")
}
certs := parsedCrl.TBSCertList.RevokedCertificates
if len(certs) != 1 {
t.Fatal("failed to get one certificate")
}
cert := certs[0]
if cert.SerialNumber.String() != "1" {
t.Fatal("cert was not correctly inserted in CRL, serial was ", cert.SerialNumber)
}
}

102
vendor/github.com/cloudflare/cfssl/api/gencrl/gencrl.go generated vendored Normal file
View File

@@ -0,0 +1,102 @@
// Package gencrl implements the HTTP handler for the gencrl commands.
package gencrl
import (
"crypto/rand"
"crypto/x509/pkix"
"encoding/json"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/log"
"io/ioutil"
"math/big"
"net/http"
"strconv"
"strings"
"time"
)
// This type is meant to be unmarshalled from JSON
type jsonCRLRequest struct {
Certificate string `json:"certificate"`
SerialNumber []string `json:"serialNumber"`
PrivateKey string `json:"issuingKey"`
ExpiryTime string `json:"expireTime"`
}
// Handle responds to requests for crl generation. It creates this crl
// based off of the given certificate, serial numbers, and private key
func gencrlHandler(w http.ResponseWriter, r *http.Request) error {
var revokedCerts []pkix.RevokedCertificate
var oneWeek = time.Duration(604800) * time.Second
var newExpiryTime = time.Now()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
r.Body.Close()
req := &jsonCRLRequest{}
err = json.Unmarshal(body, req)
if err != nil {
log.Error(err)
}
if req.ExpiryTime != "" {
expiryTime := strings.TrimSpace(req.ExpiryTime)
expiryInt, err := strconv.ParseInt(expiryTime, 0, 32)
if err != nil {
return err
}
newExpiryTime = time.Now().Add((time.Duration(expiryInt) * time.Second))
}
if req.ExpiryTime == "" {
newExpiryTime = time.Now().Add(oneWeek)
}
if err != nil {
return err
}
cert, err := helpers.ParseCertificatePEM([]byte(req.Certificate))
if err != nil {
log.Error("error from ParseCertificatePEM", err)
return errors.NewBadRequestString("malformed certificate")
}
for _, value := range req.SerialNumber {
tempBigInt := new(big.Int)
tempBigInt.SetString(value, 10)
tempCert := pkix.RevokedCertificate{
SerialNumber: tempBigInt,
RevocationTime: time.Now(),
}
revokedCerts = append(revokedCerts, tempCert)
}
key, err := helpers.ParsePrivateKeyPEM([]byte(req.PrivateKey))
if err != nil {
log.Debug("malformed private key %v", err)
return errors.NewBadRequestString("malformed Private Key")
}
result, err := cert.CreateCRL(rand.Reader, key, revokedCerts, time.Now(), newExpiryTime)
if err != nil {
log.Debug("unable to create CRL: %v", err)
return err
}
return api.SendResponse(w, result)
}
// NewHandler returns a new http.Handler that handles a crl generation request.
func NewHandler() http.Handler {
return api.HTTPHandler{
Handler: api.HandlerFunc(gencrlHandler),
Methods: []string{"POST"},
}
}

View File

@@ -0,0 +1,107 @@
package gencrl
import (
"bytes"
"encoding/json"
"github.com/cloudflare/cfssl/api"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
const (
cert = "../../crl/testdata/caTwo.pem"
key = "../../crl/testdata/ca-keyTwo.pem"
serialList = "../../crl/testdata/serialList"
expiryTime = "2000"
)
type testJSON struct {
Certificate string
SerialNumber []string
PrivateKey string
ExpiryTime string
ExpectedHTTPStatus int
ExpectedSuccess bool
}
var tester = testJSON{
Certificate: cert,
SerialNumber: []string{"1", "2", "3"},
PrivateKey: key,
ExpiryTime: "2000",
ExpectedHTTPStatus: 200,
ExpectedSuccess: true,
}
func newTestHandler(t *testing.T) http.Handler {
return NewHandler()
}
func TestNewHandler(t *testing.T) {
newTestHandler(t)
}
func newCRLServer(t *testing.T) *httptest.Server {
ts := httptest.NewServer(newTestHandler(t))
return ts
}
func testCRLCreation(t *testing.T, issuingKey, certFile string, expiry string, serialList []string) (resp *http.Response, body []byte) {
ts := newCRLServer(t)
defer ts.Close()
obj := map[string]interface{}{}
if certFile != "" {
c, err := ioutil.ReadFile(certFile)
if err != nil {
t.Fatal(err)
}
obj["certificate"] = string(c)
}
obj["serialNumber"] = serialList
if issuingKey != "" {
c, err := ioutil.ReadFile(issuingKey)
if err != nil {
t.Fatal(err)
}
obj["issuingKey"] = string(c)
}
obj["expireTime"] = expiry
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 TestCRL(t *testing.T) {
resp, body := testCRLCreation(t, tester.PrivateKey, tester.Certificate, tester.ExpiryTime, tester.SerialNumber)
if resp.StatusCode != tester.ExpectedHTTPStatus {
t.Logf("expected: %d, have %d", tester.ExpectedHTTPStatus, resp.StatusCode)
t.Fatal(resp.Status, tester.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, tester.ExpectedHTTPStatus, message)
}
}

View File

@@ -0,0 +1,319 @@
// Package generator implements the HTTP handlers for certificate generation.
package generator
import (
"crypto/md5"
"crypto/sha1"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"io/ioutil"
"net/http"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/bundler"
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/csr"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/signer"
"github.com/cloudflare/cfssl/signer/universal"
)
const (
// CSRNoHostMessage is used to alert the user to a certificate lacking a hosts field.
CSRNoHostMessage = `This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see 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").`
// NoBundlerMessage is used to alert the user that the server does not have a bundler initialized.
NoBundlerMessage = `This request requires a bundler, but one is not initialized for the API server.`
)
// 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},
},
Methods: []string{"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")
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Warningf("failed to read request body: %v", err)
return errors.NewBadRequest(err)
}
r.Body.Close()
req := new(csr.CertificateRequest)
req.KeyRequest = csr.NewBasicKeyRequest()
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
bundler *bundler.Bundler
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, Methods: []string{"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,
},
Methods: []string{"POST"},
}
}
// SetBundler allows injecting an optional Bundler into the CertGeneratorHandler.
func (cg *CertGeneratorHandler) SetBundler(caBundleFile, intBundleFile string) (err error) {
cg.bundler, err = bundler.NewBundler(caBundleFile, intBundleFile)
return err
}
type genSignRequest struct {
Request *csr.CertificateRequest `json:"request"`
Profile string `json:"profile"`
Label string `json:"label"`
Bundle bool `json:"bundle"`
}
// 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)
req.Request = csr.New()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Warningf("failed to read request body: %v", err)
return errors.NewBadRequest(err)
}
r.Body.Close()
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
}
signReq := signer.SignRequest{
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(certBytes),
"sums": map[string]Sum{
"certificate_request": reqSum,
"certificate": certSum,
},
}
if req.Bundle {
if cg.bundler == nil {
return api.SendResponseWithMessage(w, result, NoBundlerMessage,
errors.New(errors.PolicyError, errors.InvalidRequest).ErrorCode)
}
bundle, err := cg.bundler.BundleFromPEMorDER(certBytes, nil, bundler.Optimal, "")
if err != nil {
return err
}
result["bundle"] = bundle
}
if len(req.Request.Hosts) == 0 {
return api.SendResponseWithMessage(w, result, CSRNoHostMessage,
errors.New(errors.PolicyError, errors.InvalidRequest).ErrorCode)
}
return api.SendResponse(w, result)
}
// CSRValidate does nothing and will never return an error. It exists because NewHandler
// requires a Validator as a parameter.
func CSRValidate(req *csr.CertificateRequest) error {
return nil
}

View File

@@ -0,0 +1,145 @@
package generator
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/csr"
"github.com/cloudflare/cfssl/signer/local"
)
const (
testCaFile = "testdata/ca.pem"
testCaKeyFile = "testdata/ca_key.pem"
testCABundle = "../../bundler/testdata/ca-bundle.pem"
testIntBundle = "../../bundler/testdata/int-bundle.pem"
)
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.NewBasicKeyRequest(),
}
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)
}
}
func TestCSRValidate(t *testing.T) {
req := &csr.CertificateRequest{
Names: []csr.Name{
{
C: "US",
ST: "California",
L: "San Francisco",
O: "CloudFlare",
OU: "Systems Engineering",
},
},
CN: "cloudflare.com",
Hosts: []string{},
KeyRequest: csr.NewBasicKeyRequest(),
}
err := CSRValidate(req)
if err != nil {
t.Fatal("There should be not an error for missing Hosts parameter")
}
}
func TestNewCertGeneratorHandlerFromSigner(t *testing.T) {
var expiry = 1 * time.Minute
var CAConfig = &config.Config{
Signing: &config.Signing{
Profiles: map[string]*config.SigningProfile{
"signature": {
Usage: []string{"digital signature"},
Expiry: expiry,
},
},
Default: &config.SigningProfile{
Usage: []string{"cert sign", "crl sign"},
ExpiryString: "43800h",
Expiry: expiry,
CAConstraint: config.CAConstraint{IsCA: true},
ClientProvidesSerialNumbers: true,
},
},
}
s, err := local.NewSignerFromFile(testCaFile, testCaKeyFile, CAConfig.Signing)
if err != nil {
t.Fatal(err)
}
h := NewCertGeneratorHandlerFromSigner(CSRValidate, s)
_, ok := h.(http.Handler)
if !ok {
t.Fatal("A HTTP handler has not been returned")
}
apiH, ok := h.(api.HTTPHandler)
if !ok {
t.Fatal("An api.HTTPHandler has not been returned")
}
cg, ok := apiH.Handler.(*CertGeneratorHandler)
if !ok {
t.Fatal("A CertGeneratorHandler has not been set")
}
if err := cg.SetBundler(testCABundle, testIntBundle); err != nil {
t.Fatal(err)
}
}

121
vendor/github.com/cloudflare/cfssl/api/info/info.go generated vendored Normal file
View File

@@ -0,0 +1,121 @@
// Package info implements the HTTP handler for the info command.
package info
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/info"
"github.com/cloudflare/cfssl/log"
"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,
},
Methods: []string{"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(info.Req)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Warningf("failed to read request body: %v", err)
return errors.NewBadRequest(err)
}
r.Body.Close()
err = json.Unmarshal(body, req)
if err != nil {
log.Warningf("failed to unmarshal request: %v", err)
return errors.NewBadRequest(err)
}
resp, err := h.sign.Info(*req)
if err != nil {
return err
}
response := api.NewSuccessResponse(resp)
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
return enc.Encode(response)
}
// MultiHandler is a handler for providing the public certificates for
// a multi-root certificate authority. It takes a mapping of label to
// signer and a default label, and handles the standard information
// request as defined in the client package.
type MultiHandler struct {
signers map[string]signer.Signer
defaultLabel string
}
// NewMultiHandler constructs a MultiHandler from a mapping of labels
// to signers and the default label.
func NewMultiHandler(signers map[string]signer.Signer, defaultLabel string) (http.Handler, error) {
return &api.HTTPHandler{
Handler: &MultiHandler{
signers: signers,
defaultLabel: defaultLabel,
},
Methods: []string{"POST"},
}, nil
}
// Handle accepts client information requests, and uses the label to
// look up the signer whose public certificate should be retrieved. If
// the label is empty, the default label is used.
func (h *MultiHandler) Handle(w http.ResponseWriter, r *http.Request) error {
req := new(info.Req)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Warningf("failed to read request body: %v", err)
return errors.NewBadRequest(err)
}
r.Body.Close()
err = json.Unmarshal(body, req)
if err != nil {
log.Warningf("failed to unmarshal request: %v", err)
return errors.NewBadRequest(err)
}
log.Debug("checking label")
if req.Label == "" {
req.Label = h.defaultLabel
}
if _, ok := h.signers[req.Label]; !ok {
log.Warningf("request for invalid endpoint")
return errors.NewBadRequestString("bad label")
}
log.Debug("getting info")
resp, err := h.signers[req.Label].Info(*req)
if err != nil {
log.Infof("error getting certificate: %v", err)
return err
}
response := api.NewSuccessResponse(resp)
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
return enc.Encode(response)
}

View File

@@ -0,0 +1,256 @@
package info
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/signer"
"github.com/cloudflare/cfssl/signer/local"
)
const (
testCaFile = "../testdata/ca.pem"
testCaKeyFile = "../testdata/ca_key.pem"
// second test CA for multiroot
testCaFile2 = "../testdata/ca2.pem"
testCaKeyFile2 = "../testdata/ca2-key.pem"
)
// Generally, the single root function and its multiroot analogue will
// be presented together.
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 newTestMultiHandler(t *testing.T) (h http.Handler) {
signer1, err := local.NewSignerFromFile(testCaFile, testCaKeyFile, nil)
if err != nil {
t.Fatal(err)
}
signer2, err := local.NewSignerFromFile(testCaFile2, testCaKeyFile2, nil)
if err != nil {
t.Fatal(err)
}
signers := map[string]signer.Signer{
"test1": signer1,
"test2": signer2,
}
h, err = NewMultiHandler(signers, "test1")
if err != nil {
t.Fatalf("%v", err)
}
return
}
func TestNewHandler(t *testing.T) {
newTestHandler(t)
}
func TestNewMultiHandler(t *testing.T) {
newTestMultiHandler(t)
}
func newInfoServer(t *testing.T) *httptest.Server {
ts := httptest.NewServer(newTestHandler(t))
return ts
}
func newMultiInfoServer(t *testing.T) *httptest.Server {
return httptest.NewServer(newTestMultiHandler(t))
}
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
}
func testMultiInfoFile(t *testing.T, req map[string]interface{}) (resp *http.Response, body []byte) {
ts := newMultiInfoServer(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": 123,
},
http.StatusBadRequest,
false,
http.StatusBadRequest,
},
}
var multiInfoTests = []infoTest{
{
map[string]interface{}{
"label": "",
"profile": "",
},
http.StatusOK,
true,
0,
},
{
map[string]interface{}{
"label": "test1",
"profile": "",
},
http.StatusOK,
true,
0,
},
{
map[string]interface{}{
"label": "test2",
"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)
}
}
}
func TestMultiInfo(t *testing.T) {
for i, test := range multiInfoTests {
resp, body := testMultiInfoFile(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,61 @@
// Package initca implements the HTTP handler for the CA initialization command
package initca
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/csr"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/initca"
"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")
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Warningf("failed to read request body: %v", err)
return errors.NewBadRequest(err)
}
r.Body.Close()
req := new(csr.CertificateRequest)
req.KeyRequest = csr.NewBasicKeyRequest()
err = json.Unmarshal(body, req)
if err != nil {
log.Warningf("failed to unmarshal request: %v", err)
return errors.NewBadRequest(err)
}
cert, _, key, 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), Methods: []string{"POST"}}
}

View File

@@ -0,0 +1,90 @@
package initca
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"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.NewBasicKeyRequest(),
}
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)
}
}
func TestBadRequestBody(t *testing.T) {
ts := httptest.NewServer(NewHandler())
req, _ := http.NewRequest("POST", ts.URL, nil)
resp, _ := http.DefaultClient.Do(req)
if resp.StatusCode == http.StatusOK {
t.Fatal(resp.Status)
}
}
func TestBadRequestBody_2(t *testing.T) {
ts := httptest.NewServer(NewHandler())
r := &csr.CertificateRequest{}
csrBytes, err := json.Marshal(r)
if err != nil {
t.Fatal(err)
}
data := bytes.NewReader(csrBytes)
req, _ := http.NewRequest("POST", ts.URL, data)
resp, _ := http.DefaultClient.Do(req)
if resp.StatusCode == http.StatusOK {
t.Fatal(resp.Status)
}
}

113
vendor/github.com/cloudflare/cfssl/api/ocsp/ocspsign.go generated vendored Normal file
View File

@@ -0,0 +1,113 @@
// Package ocsp implements the HTTP handler for the ocsp commands.
package ocsp
import (
"crypto"
"net/http"
"encoding/base64"
"encoding/json"
"io/ioutil"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/ocsp"
)
// A Handler accepts requests with a certficate parameter
// (which should be PEM-encoded) and returns a signed ocsp
// response.
type Handler struct {
signer ocsp.Signer
}
// NewHandler returns a new http.Handler that handles a ocspsign request.
func NewHandler(s ocsp.Signer) http.Handler {
return &api.HTTPHandler{
Handler: &Handler{
signer: s,
},
Methods: []string{"POST"},
}
}
// This type is meant to be unmarshalled from JSON
type jsonSignRequest struct {
Certificate string `json:"certificate"`
Status string `json:"status"`
Reason int `json:"reason,omitempty"`
RevokedAt string `json:"revoked_at,omitempty"`
IssuerHash string `json:"issuer_hash,omitempty"`
}
var nameToHash = map[string]crypto.Hash{
"MD5": crypto.MD5,
"SHA1": crypto.SHA1,
"SHA256": crypto.SHA256,
"SHA384": crypto.SHA384,
"SHA512": crypto.SHA512,
}
// Handle responds to requests for a ocsp signature. It creates and signs
// a ocsp response for the provided certificate and status. If the status
// is revoked then it also adds reason and revoked_at. The response is
// base64 encoded.
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
r.Body.Close()
// Default the status to good so it matches the cli
req := &jsonSignRequest{
Status: "good",
}
err = json.Unmarshal(body, req)
if err != nil {
return errors.NewBadRequestString("Unable to parse sign request")
}
cert, err := helpers.ParseCertificatePEM([]byte(req.Certificate))
if err != nil {
log.Error("Error from ParseCertificatePEM", err)
return errors.NewBadRequestString("Malformed certificate")
}
signReq := ocsp.SignRequest{
Certificate: cert,
Status: req.Status,
}
// We need to convert the time from being a string to a time.Time
if req.Status == "revoked" {
signReq.Reason = req.Reason
// "now" is accepted and the default on the cli so default that here
if req.RevokedAt == "" || req.RevokedAt == "now" {
signReq.RevokedAt = time.Now()
} else {
signReq.RevokedAt, err = time.Parse("2006-01-02", req.RevokedAt)
if err != nil {
return errors.NewBadRequestString("Malformed revocation time")
}
}
}
if req.IssuerHash != "" {
issuerHash, ok := nameToHash[req.IssuerHash]
if !ok {
return errors.NewBadRequestString("Unsupported hash algorithm in request")
}
signReq.IssuerHash = issuerHash
}
resp, err := h.signer.Sign(signReq)
if err != nil {
return err
}
b64Resp := base64.StdEncoding.EncodeToString(resp)
result := map[string]string{"ocspResponse": b64Resp}
return api.SendResponse(w, result)
}

View File

@@ -0,0 +1,242 @@
package ocsp
import (
"bytes"
"encoding/base64"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/ocsp"
goocsp "golang.org/x/crypto/ocsp"
"github.com/cloudflare/cfssl/helpers"
)
const (
testCaFile = "../../ocsp/testdata/ca.pem"
testRespCertFile = "../../ocsp/testdata/server.crt"
testKeyFile = "../../ocsp/testdata/server.key"
testCertFile = "../../ocsp/testdata/cert.pem"
)
func newTestHandler(t *testing.T) http.Handler {
// arbitrary duration
dur, _ := time.ParseDuration("1ms")
s, err := ocsp.NewSignerFromFile(testCaFile, testRespCertFile, testKeyFile, dur)
if err != nil {
t.Fatalf("Signer creation failed %v", err)
}
return NewHandler(s)
}
func TestNewHandler(t *testing.T) {
newTestHandler(t)
}
func newSignServer(t *testing.T) *httptest.Server {
ts := httptest.NewServer(newTestHandler(t))
return ts
}
func testSignFile(t *testing.T, certFile, status string, reason int, revokedAt string, hash string) (resp *http.Response, body []byte) {
ts := newSignServer(t)
defer ts.Close()
obj := map[string]interface{}{}
if certFile != "" {
c, err := ioutil.ReadFile(certFile)
if err != nil {
t.Fatal(err)
}
obj["certificate"] = string(c)
}
if status != "" {
obj["status"] = status
}
obj["reason"] = reason
if revokedAt != "" {
obj["revoked_at"] = revokedAt
}
obj["issuer_hash"] = hash
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
}
type signTest struct {
CertificateFile string
Status string
Reason int
RevokedAt string
ExpectedHTTPStatus int
ExpectedSuccess bool
ExpectedErrorCode int
IssuerHash string
}
var signTests = []signTest{
{
CertificateFile: testCertFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertificateFile: testCertFile,
Status: "revoked",
Reason: 1,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertificateFile: testCertFile,
Status: "revoked",
RevokedAt: "now",
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertificateFile: testCertFile,
Status: "revoked",
RevokedAt: "2015-08-15",
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertificateFile: testCertFile,
Status: "revoked",
RevokedAt: "a",
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: http.StatusBadRequest,
},
{
CertificateFile: "",
Status: "",
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: http.StatusBadRequest,
},
{
CertificateFile: testCertFile,
Status: "_",
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: 8200,
},
{
CertificateFile: testCertFile,
IssuerHash: "SHA256",
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
CertificateFile: testCertFile,
IssuerHash: "MD4",
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: http.StatusBadRequest,
},
}
func TestSign(t *testing.T) {
for i, test := range signTests {
resp, body := testSignFile(t, test.CertificateFile, test.Status, test.Reason, test.RevokedAt, test.IssuerHash)
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 {
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)
}
continue
}
result, ok := message.Result.(map[string]interface{})
if !ok {
t.Logf("failed to read result")
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}
b64Resp, ok := result["ocspResponse"].(string)
if !ok {
t.Logf("failed to find ocspResponse")
t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body))
}
der, err := base64.StdEncoding.DecodeString(b64Resp)
if err != nil {
t.Logf("failed to decode base64")
t.Fatal(resp.Status, test.ExpectedHTTPStatus, b64Resp)
}
ocspResp, err := goocsp.ParseResponse(der, nil)
if err != nil {
t.Logf("failed to parse ocsp response: %v", err)
t.Fatal(resp.Status, test.ExpectedHTTPStatus, b64Resp)
}
//should default to good
if test.Status == "" {
test.Status = "good"
}
intStatus := ocsp.StatusCode[test.Status]
if ocspResp.Status != intStatus {
t.Fatalf("Test %d incorrect status: expected: %v, have %v", i, intStatus, ocspResp.Status)
t.Fatal(ocspResp.Status, intStatus, ocspResp)
}
if test.Status == "revoked" {
if ocspResp.RevocationReason != test.Reason {
t.Fatalf("Test %d incorrect reason: expected: %v, have %v", i, test.Reason, ocspResp.RevocationReason)
t.Fatal(ocspResp.RevocationReason, test.Reason, ocspResp)
}
var r time.Time
if test.RevokedAt == "" || test.RevokedAt == "now" {
r = time.Now().UTC().Truncate(helpers.OneDay)
} else {
r, _ = time.Parse("2006-01-02", test.RevokedAt)
}
if !ocspResp.RevokedAt.Truncate(helpers.OneDay).Equal(r) {
t.Fatalf("Test %d incorrect revokedAt: expected: %v, have %v", i, r, ocspResp.RevokedAt)
t.Fatal(ocspResp.RevokedAt, test.RevokedAt, ocspResp)
}
}
}
}

137
vendor/github.com/cloudflare/cfssl/api/revoke/revoke.go generated vendored Normal file
View File

@@ -0,0 +1,137 @@
// Package revoke implements the HTTP handler for the revoke command
package revoke
import (
"encoding/json"
"io/ioutil"
"net/http"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/certdb"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/ocsp"
stdocsp "golang.org/x/crypto/ocsp"
)
// A Handler accepts requests with a serial number parameter
// and revokes
type Handler struct {
dbAccessor certdb.Accessor
Signer ocsp.Signer
}
// NewHandler returns a new http.Handler that handles a revoke request.
func NewHandler(dbAccessor certdb.Accessor) http.Handler {
return &api.HTTPHandler{
Handler: &Handler{
dbAccessor: dbAccessor,
},
Methods: []string{"POST"},
}
}
// NewOCSPHandler returns a new http.Handler that handles a revoke
// request and also generates an OCSP response
func NewOCSPHandler(dbAccessor certdb.Accessor, signer ocsp.Signer) http.Handler {
return &api.HTTPHandler{
Handler: &Handler{
dbAccessor: dbAccessor,
Signer: signer,
},
Methods: []string{"POST"},
}
}
// This type is meant to be unmarshalled from JSON
type jsonRevokeRequest struct {
Serial string `json:"serial"`
AKI string `json:"authority_key_id"`
Reason string `json:"reason"`
}
// Handle responds to revocation requests. It attempts to revoke
// a certificate with a given serial number
func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
r.Body.Close()
// Default the status to good so it matches the cli
var req jsonRevokeRequest
err = json.Unmarshal(body, &req)
if err != nil {
return errors.NewBadRequestString("Unable to parse revocation request")
}
if len(req.Serial) == 0 {
return errors.NewBadRequestString("serial number is required but not provided")
}
var reasonCode int
reasonCode, err = ocsp.ReasonStringToCode(req.Reason)
if err != nil {
return errors.NewBadRequestString("Invalid reason code")
}
err = h.dbAccessor.RevokeCertificate(req.Serial, req.AKI, reasonCode)
if err != nil {
return err
}
// If we were given a signer, try and generate an OCSP
// response indicating revocation
if h.Signer != nil {
// TODO: should these errors be errors?
// Grab the certificate from the database
cr, err := h.dbAccessor.GetCertificate(req.Serial, req.AKI)
if err != nil {
return err
}
if len(cr) != 1 {
return errors.NewBadRequestString("No unique certificate found")
}
cert, err := helpers.ParseCertificatePEM([]byte(cr[0].PEM))
if err != nil {
return errors.NewBadRequestString("Unable to parse certificates from PEM data")
}
sr := ocsp.SignRequest{
Certificate: cert,
Status: "revoked",
Reason: reasonCode,
RevokedAt: time.Now().UTC(),
}
ocspResponse, err := h.Signer.Sign(sr)
if err != nil {
return err
}
// We parse the OCSP repsonse in order to get the next
// update time/expiry time
ocspParsed, err := stdocsp.ParseResponse(ocspResponse, nil)
if err != nil {
return err
}
ocspRecord := certdb.OCSPRecord{
Serial: req.Serial,
AKI: req.AKI,
Body: string(ocspResponse),
Expiry: ocspParsed.NextUpdate,
}
if err = h.dbAccessor.InsertOCSP(ocspRecord); err != nil {
return err
}
}
result := map[string]string{}
return api.SendResponse(w, result)
}

View File

@@ -0,0 +1,295 @@
package revoke
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/json"
"encoding/pem"
"io/ioutil"
"math/big"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/certdb"
"github.com/cloudflare/cfssl/certdb/sql"
"github.com/cloudflare/cfssl/certdb/testdb"
"github.com/cloudflare/cfssl/ocsp"
stdocsp "golang.org/x/crypto/ocsp"
)
const (
fakeAKI = "fake aki"
)
func prepDB() (certdb.Accessor, error) {
db := testdb.SQLiteDB("../../certdb/testdb/certstore_development.db")
expirationTime := time.Now().AddDate(1, 0, 0)
var cert = certdb.CertificateRecord{
Serial: "1",
AKI: fakeAKI,
Expiry: expirationTime,
PEM: "unexpired cert",
}
dbAccessor := sql.NewAccessor(db)
err := dbAccessor.InsertCertificate(cert)
if err != nil {
return nil, err
}
return dbAccessor, nil
}
func testRevokeCert(t *testing.T, dbAccessor certdb.Accessor, serial, aki, reason string) (resp *http.Response, body []byte) {
ts := httptest.NewServer(NewHandler(dbAccessor))
defer ts.Close()
obj := map[string]interface{}{}
obj["serial"] = serial
obj["authority_key_id"] = aki
if reason != "" {
obj["reason"] = reason
}
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 TestInvalidRevocation(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
resp, _ := testRevokeCert(t, dbAccessor, "", "", "")
if resp.StatusCode != http.StatusBadRequest {
t.Fatal("expected bad request response")
}
}
func TestRevocation(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
resp, body := testRevokeCert(t, dbAccessor, "1", fakeAKI, "5")
if resp.StatusCode != http.StatusOK {
t.Fatal("unexpected HTTP status code; expected OK", string(body))
}
message := new(api.Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
certs, err := dbAccessor.GetCertificate("1", fakeAKI)
if err != nil {
t.Fatal("failed to get certificate ", err)
}
if len(certs) != 1 {
t.Fatal("failed to get one certificate")
}
cert := certs[0]
if cert.Status != "revoked" || cert.Reason != 5 {
t.Fatal("cert was not correctly revoked")
}
}
// TestOCSPGeneration tests that revoking a certificate (when the
// request handler has an OCSP response signer) generates an
// appropriate OCSP response in the certdb.
func TestOCSPGeneration(t *testing.T) {
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
serialNumberRange := new(big.Int).Lsh(big.NewInt(1), 128)
// 1. Generate a CA certificate to serve as the signing certificate.
issuerSerial, err := rand.Int(rand.Reader, serialNumberRange)
if err != nil {
t.Fatal(err)
}
issuerTemplate := x509.Certificate{
SerialNumber: issuerSerial,
Subject: pkix.Name{
Organization: []string{"cfssl unit test"},
},
AuthorityKeyId: []byte{42, 42, 42, 42},
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
IsCA: true,
BasicConstraintsValid: true,
}
issuerBytes, err := x509.CreateCertificate(rand.Reader, &issuerTemplate, &issuerTemplate, &privKey.PublicKey, privKey)
if err != nil {
t.Fatal(err)
}
issuer, err := x509.ParseCertificate(issuerBytes)
if err != nil {
t.Fatal(err)
}
// 2. Generate a certificate signed by the CA certificate to revoke.
revokedSerial, err := rand.Int(rand.Reader, serialNumberRange)
if err != nil {
t.Fatal(err)
}
revokedTemplate := x509.Certificate{
SerialNumber: revokedSerial,
Subject: pkix.Name{
Organization: []string{"Cornell CS 5152"},
},
AuthorityKeyId: []byte{42, 42, 42, 42},
}
revokedBytes, err := x509.CreateCertificate(rand.Reader, &revokedTemplate, issuer, &privKey.PublicKey, privKey)
if err != nil {
t.Fatal(err)
}
revoked := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: revokedBytes,
})
revokedAKI := hex.EncodeToString(revokedTemplate.AuthorityKeyId)
revokedSerialStr := revokedSerial.Text(16)
// 3. Generate a certificate to use as the responder certificate.
responderSerial, err := rand.Int(rand.Reader, serialNumberRange)
if err != nil {
t.Fatal(err)
}
responderTemplate := x509.Certificate{
SerialNumber: responderSerial,
Subject: pkix.Name{
Organization: []string{"Cornell CS 5152 Responder"},
},
AuthorityKeyId: []byte{42, 42, 42, 43},
}
responderBytes, err := x509.CreateCertificate(rand.Reader, &responderTemplate, &responderTemplate, &privKey.PublicKey, privKey)
if err != nil {
t.Fatal(err)
}
responder, err := x509.ParseCertificate(responderBytes)
if err != nil {
t.Fatal(err)
}
// 4. Create the OCSP signer
signer, err := ocsp.NewSigner(issuer, responder, privKey, time.Hour)
if err != nil {
t.Fatal(err)
}
// 5. Spin up the test server
// 5a. Prepare the DB
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}
expirationTime := time.Now().AddDate(1, 0, 0)
cr := certdb.CertificateRecord{
Serial: revokedSerialStr,
AKI: revokedAKI,
Expiry: expirationTime,
PEM: string(revoked),
}
if err := dbAccessor.InsertCertificate(cr); err != nil {
t.Fatal(err)
}
// 5b. Start the test server
ts := httptest.NewServer(NewOCSPHandler(dbAccessor, signer))
defer ts.Close()
// 6. Prepare the revocation request
obj := map[string]interface{}{}
obj["serial"] = revokedSerialStr
obj["authority_key_id"] = revokedAKI
obj["reason"] = "unspecified"
blob, err := json.Marshal(obj)
if err != nil {
t.Fatal(err)
}
// Get the original number of OCSP responses
ocspsBefore, _ := dbAccessor.GetOCSP(revokedSerialStr, revokedAKI)
ocspCountBefore := len(ocspsBefore)
// 7. Send the revocation request
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)
}
if resp.StatusCode != http.StatusOK {
t.Fatal("unexpected HTTP status code; expected OK", string(body))
}
message := new(api.Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
// 8. Make sure the certificate record was updated
certs, err := dbAccessor.GetCertificate(revokedSerialStr, revokedAKI)
if err != nil {
t.Fatal("failed to get certificate ", err)
}
if len(certs) != 1 {
t.Fatal("failed to get one certificate")
}
cert := certs[0]
if cert.Status != "revoked" || cert.Reason != stdocsp.Unspecified {
t.Fatal("cert was not correctly revoked")
}
// 9. Make sure there is an OCSP record
ocsps, err := dbAccessor.GetOCSP(revokedSerialStr, revokedAKI)
if err != nil {
t.Fatal("failed to get OCSP responses ", err)
}
if len(ocsps) == 0 {
t.Fatal("No OCSP response generated")
}
if len(ocsps) <= ocspCountBefore {
t.Fatal("No new OCSP response found")
}
}

76
vendor/github.com/cloudflare/cfssl/api/scan/scan.go generated vendored Normal file
View File

@@ -0,0 +1,76 @@
package scan
import (
"encoding/json"
"net/http"
"time"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/scan"
)
// scanHandler is an HTTP handler that accepts GET parameters for host (required)
// family and scanner, and uses these to perform scans, returning a JSON blob result.
func scanHandler(w http.ResponseWriter, r *http.Request) error {
if err := r.ParseForm(); err != nil {
log.Warningf("failed to parse body: %v", err)
return errors.NewBadRequest(err)
}
family := r.Form.Get("family")
scanner := r.Form.Get("scanner")
ip := r.Form.Get("ip")
timeoutStr := r.Form.Get("timeout")
var timeout time.Duration
var err error
if timeoutStr != "" {
if timeout, err = time.ParseDuration(timeoutStr); err != nil {
return errors.NewBadRequest(err)
}
if timeout < time.Second || timeout > 5*time.Minute {
return errors.NewBadRequestString("invalid timeout given")
}
} else {
timeout = time.Minute
}
host := r.Form.Get("host")
if host == "" {
log.Warningf("no host given")
return errors.NewBadRequestString("no host given")
}
results, err := scan.Default.RunScans(host, ip, family, scanner, timeout)
if err != nil {
return errors.NewBadRequest(err)
}
return json.NewEncoder(w).Encode(api.NewSuccessResponse(results))
}
// NewHandler returns a new http.Handler that handles a scan request.
func NewHandler(caBundleFile string) (http.Handler, error) {
return api.HTTPHandler{
Handler: api.HandlerFunc(scanHandler),
Methods: []string{"GET"},
}, scan.LoadRootCAs(caBundleFile)
}
// scanInfoHandler is an HTTP handler that returns a JSON blob result describing
// the possible families and scans to be run.
func scanInfoHandler(w http.ResponseWriter, r *http.Request) error {
log.Info("setting up scaninfo handler")
response := api.NewSuccessResponse(scan.Default)
enc := json.NewEncoder(w)
return enc.Encode(response)
}
// NewInfoHandler returns a new http.Handler that handles a request for scan info.
func NewInfoHandler() http.Handler {
return api.HTTPHandler{
Handler: api.HandlerFunc(scanInfoHandler),
Methods: []string{"GET"},
}
}

View File

@@ -0,0 +1,63 @@
package scan
import (
"net/http"
"net/http/httptest"
"testing"
)
var (
handler, _ = NewHandler("")
ts = httptest.NewServer(handler)
)
func TestBadRequest(t *testing.T) {
// Test request with no host
req, _ := http.NewRequest("GET", ts.URL, nil)
resp, _ := http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusBadRequest {
t.Fatal(resp.Status)
}
}
func TestScanRESTfulVerbs(t *testing.T) {
// GET should work
req, _ := http.NewRequest("GET", ts.URL, nil)
data := req.URL.Query()
data.Add("host", "cloudflare.com")
req.URL.RawQuery = data.Encode()
resp, _ := http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusOK {
t.Fatal(resp.Status)
}
// POST, PUT, DELETE, WHATEVER should return 400 errors
req, _ = http.NewRequest("POST", ts.URL, nil)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("DELETE", ts.URL, nil)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("PUT", ts.URL, nil)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
req, _ = http.NewRequest("WHATEVER", ts.URL, nil)
resp, _ = http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatal(resp.Status)
}
}
func TestNewInfoHandler(t *testing.T) {
handler := NewInfoHandler()
if handler == nil {
t.Fatal("Handler error")
}
}

51
vendor/github.com/cloudflare/cfssl/api/sign/sign.go generated vendored Normal file
View File

@@ -0,0 +1,51 @@
// Package sign implements the HTTP handler for the certificate signing command.
package sign
import (
"net/http"
"github.com/cloudflare/cfssl/api/signhandler"
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/signer/universal"
)
// 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 signhandler.NewHandlerFromSigner(s)
}
// 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 signhandler.NewAuthHandlerFromSigner(s)
}

View File

@@ -0,0 +1,531 @@
package sign
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/auth"
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/signer"
)
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"
}
}
}`
var validMixedLocalConfig = `
{
"signing": {
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "1m"
},
"profiles": {
"auth": {
"usages": ["digital signature", "email protection"],
"expiry": "1m",
"auth_key": "sample"
}
}
},
"auth_keys": {
"sample": {
"type":"standard",
"key":"0123456789ABCDEF0123456789ABCDEF"
}
}
}`
var alsoValidMixedLocalConfig = `
{
"signing": {
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "1m",
"auth_key": "sample"
},
"profiles": {
"no-auth": {
"usages": ["digital signature", "email protection"],
"expiry": "1m"
}
}
},
"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 TestNewAuthHandlerWithNonAuthProfile(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("No profile have auth keys. Should have failed to create auth sign handler.")
}
}
func TestNewHandlersWithMixedProfile(t *testing.T) {
conf, err := config.LoadConfig([]byte(validMixedLocalConfig))
if err != nil {
t.Fatal(err)
}
_, err = NewHandler(testCaFile, testCaKeyFile, conf.Signing)
if err != nil {
t.Fatal("Should be able to create non-auth sign handler.")
}
_, err = NewAuthHandler(testCaFile, testCaKeyFile, conf.Signing)
if err != nil {
t.Fatal("Should be able to create auth sign handler.")
}
}
func TestNewHandlersWithAnotherMixedProfile(t *testing.T) {
conf, err := config.LoadConfig([]byte(alsoValidMixedLocalConfig))
if err != nil {
t.Fatal(err)
}
_, err = NewHandler(testCaFile, testCaKeyFile, conf.Signing)
if err != nil {
t.Fatal("Should be able to create non-auth sign handler.")
}
_, err = NewAuthHandler(testCaFile, testCaKeyFile, conf.Signing)
if err != nil {
t.Fatal("Should be able to create auth sign handler.")
}
}
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, hosts []string, subject *signer.Subject, 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 hosts != nil {
obj["hosts"] = hosts
}
if len(csrPEM) > 0 {
obj["certificate_request"] = string(csrPEM)
}
if subject != nil {
obj["subject"] = subject
}
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 {
Hosts []string
Subject *signer.Subject
CSRFile string
ExpectedHTTPStatus int
ExpectedSuccess bool
ExpectedErrorCode int
}
var signTests = []signTest{
{
Hosts: []string{testHostName},
CSRFile: testCSRFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
Hosts: []string{testDomainName},
CSRFile: testCSRFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
Hosts: []string{testDomainName, testHostName},
CSRFile: testCSRFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
Hosts: []string{testDomainName},
Subject: &signer.Subject{CN: "example.com"},
CSRFile: testCSRFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
Hosts: []string{},
Subject: &signer.Subject{CN: "example.com"},
CSRFile: testCSRFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
Hosts: nil,
CSRFile: testCSRFile,
ExpectedHTTPStatus: http.StatusOK,
ExpectedSuccess: true,
ExpectedErrorCode: 0,
},
{
Hosts: []string{testDomainName},
CSRFile: "",
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: http.StatusBadRequest,
},
{
Hosts: []string{testDomainName},
CSRFile: testBrokenCSRFile,
ExpectedHTTPStatus: http.StatusBadRequest,
ExpectedSuccess: false,
ExpectedErrorCode: 9002,
},
}
func TestSign(t *testing.T) {
for i, test := range signTests {
resp, body := testSignFile(t, test.Hosts, test.Subject, 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 {
// an empty hostname is not accepted by the old interface but an empty hosts array should be accepted
// so skip the case of empty hosts array for the old interface.
if test.Hosts != nil && len(test.Hosts) == 0 {
continue
}
hostname := strings.Join(test.Hosts, ",")
resp, body := testSignFileOldInterface(t, 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, hosts []string, subject *signer.Subject, 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]interface{}{}
if hosts != nil {
obj["hosts"] = hosts
}
if subject != nil {
obj["subject"] = subject
}
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.Hosts, test.Subject, 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,294 @@
// Package signhandler provides the handlers for signers.
package signhandler
import (
"encoding/json"
"io/ioutil"
"math/big"
"net/http"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/auth"
"github.com/cloudflare/cfssl/bundler"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/signer"
)
// NoBundlerMessage is used to alert the user that the server does not have a bundler initialized.
const NoBundlerMessage = `This request requires a bundler, but one is not initialized for the API server.`
// 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
bundler *bundler.Bundler
}
// 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 {
haveUnauth = haveUnauth || (profile.Provider == nil)
}
if !haveUnauth {
err = errors.New(errors.PolicyError, errors.InvalidPolicy)
return
}
return &api.HTTPHandler{
Handler: &Handler{
signer: signer,
},
Methods: []string{"POST"},
}, nil
}
// SetBundler allows injecting an optional Bundler into the Handler.
func (h *Handler) SetBundler(caBundleFile, intBundleFile string) (err error) {
h.bundler, err = bundler.NewBundler(caBundleFile, intBundleFile)
return err
}
// 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"`
Serial *big.Int `json:"serial,omitempty"`
Bundle bool `json:"bundle"`
}
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,
Serial: js.Serial,
}
}
return signer.SignRequest{
Hosts: js.Hosts,
Subject: sub,
Request: js.Request,
Profile: js.Profile,
Label: js.Label,
Serial: js.Serial,
}
}
// 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 req.Request == "" {
return errors.NewBadRequestString("missing parameter 'certificate_request'")
}
var cert []byte
profile, err := signer.Profile(h.signer, req.Profile)
if err != nil {
return err
}
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]interface{}{"certificate": string(cert)}
if req.Bundle {
if h.bundler == nil {
return api.SendResponseWithMessage(w, result, NoBundlerMessage,
errors.New(errors.PolicyError, errors.InvalidRequest).ErrorCode)
}
bundle, err := h.bundler.BundleFromPEMorDER(cert, nil, bundler.Optimal, "")
if err != nil {
return err
}
result["bundle"] = bundle
}
log.Info("wrote response")
return api.SendResponse(w, result)
}
// An AuthHandler verifies and signs incoming signature requests.
type AuthHandler struct {
signer signer.Signer
bundler *bundler.Bundler
}
// 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,
},
Methods: []string{"POST"},
}, nil
}
// SetBundler allows injecting an optional Bundler into the Handler.
func (h *AuthHandler) SetBundler(caBundleFile, intBundleFile string) (err error) {
h.bundler, err = bundler.NewBundler(caBundleFile, intBundleFile)
return err
}
// 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")
}
// 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, err := signer.Profile(h.signer, req.Profile)
if err != nil {
return err
}
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")
}
signReq := jsonReqToTrue(req)
if signReq.Request == "" {
return errors.NewBadRequestString("missing parameter 'certificate_request'")
}
cert, err := h.signer.Sign(signReq)
if err != nil {
log.Errorf("signature failed: %v", err)
return err
}
result := map[string]interface{}{"certificate": string(cert)}
if req.Bundle {
if h.bundler == nil {
return api.SendResponseWithMessage(w, result, NoBundlerMessage,
errors.New(errors.PolicyError, errors.InvalidRequest).ErrorCode)
}
bundle, err := h.bundler.BundleFromPEMorDER(cert, nil, bundler.Optimal, "")
if err != nil {
return err
}
result["bundle"] = bundle
}
log.Info("wrote response")
return api.SendResponse(w, result)
}

View File

@@ -0,0 +1,113 @@
package signhandler
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/certdb"
"github.com/cloudflare/cfssl/certdb/sql"
"github.com/cloudflare/cfssl/certdb/testdb"
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/signer"
"github.com/cloudflare/cfssl/signer/local"
)
const (
testCaFile = "../testdata/ca.pem"
testCaKeyFile = "../testdata/ca_key.pem"
testCSRFile = "../testdata/csr.pem"
)
// GetUnexpiredCertificates sometimes doesn't return a certificate with an
// expiry of 1m as above
var validLocalConfigLongerExpiry = `
{
"signing": {
"default": {
"usages": ["digital signature", "email protection"],
"expiry": "10m"
}
}
}`
var dbAccessor certdb.Accessor
func TestSignerDBPersistence(t *testing.T) {
conf, err := config.LoadConfig([]byte(validLocalConfigLongerExpiry))
if err != nil {
t.Fatal(err)
}
var s *local.Signer
s, err = local.NewSignerFromFile(testCaFile, testCaKeyFile, conf.Signing)
if err != nil {
t.Fatal(err)
}
db := testdb.SQLiteDB("../../certdb/testdb/certstore_development.db")
if err != nil {
t.Fatal(err)
}
dbAccessor = sql.NewAccessor(db)
s.SetDBAccessor(dbAccessor)
var handler *api.HTTPHandler
handler, err = NewHandlerFromSigner(signer.Signer(s))
if err != nil {
t.Fatal(err)
}
ts := httptest.NewServer(handler)
defer ts.Close()
var csrPEM, body []byte
csrPEM, err = ioutil.ReadFile(testCSRFile)
if err != nil {
t.Fatal(err)
}
blob, err := json.Marshal(&map[string]string{"certificate_request": string(csrPEM)})
if err != nil {
t.Fatal(err)
}
var resp *http.Response
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)
}
if resp.StatusCode != http.StatusOK {
t.Fatal(resp.Status, string(body))
}
message := new(api.Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
if !message.Success {
t.Fatal("API operation failed")
}
crs, err := dbAccessor.GetUnexpiredCertificates()
if err != nil {
t.Fatal("Failed to get unexpired certificates")
}
if len(crs) != 1 {
t.Fatal("Expected 1 unexpired certificate in the database after signing 1: len(crs)=", len(crs))
}
}

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-----

94
vendor/github.com/cloudflare/cfssl/auth/auth.go generated vendored Normal file
View File

@@ -0,0 +1,94 @@
// 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"
"fmt"
"io/ioutil"
"os"
"strings"
)
// 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) {
if splitKey := strings.SplitN(key, ":", 2); len(splitKey) == 2 {
switch splitKey[0] {
case "env":
key = os.Getenv(splitKey[1])
case "file":
data, err := ioutil.ReadFile(splitKey[1])
if err != nil {
return nil, err
}
key = string(data)
default:
return nil, fmt.Errorf("unknown key prefix: %s", splitKey[0])
}
}
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)
}

159
vendor/github.com/cloudflare/cfssl/auth/auth_test.go generated vendored Normal file
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"
}

186
vendor/github.com/cloudflare/cfssl/bundler/bundle.go generated vendored Normal file
View File

@@ -0,0 +1,186 @@
package bundler
import (
"bytes"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"time"
"github.com/cloudflare/cfssl/helpers"
)
// A Bundle contains a certificate and its trust chain. It is intended
// to store the most widely applicable chain, with shortness an
// explicit goal.
type Bundle struct {
Chain []*x509.Certificate
Cert *x509.Certificate
Root *x509.Certificate
Key interface{}
Issuer *pkix.Name
Subject *pkix.Name
Expires time.Time
LeafExpires time.Time
Hostnames []string
Status *BundleStatus
}
// BundleStatus is designated for various status reporting.
type BundleStatus struct {
// A flag on whether a new bundle is generated
IsRebundled bool `json:"rebundled"`
// A list of SKIs of expiring certificates
ExpiringSKIs []string `json:"expiring_SKIs"`
// A list of untrusted root store names
Untrusted []string `json:"untrusted_root_stores"`
// A list of human readable warning messages based on the bundle status.
Messages []string `json:"messages"`
// A status code consists of binary flags
Code int `json:"code"`
}
type chain []*x509.Certificate
func (c chain) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
for _, cert := range c {
buf.Write(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}))
}
ret := bytes.TrimSpace(buf.Bytes())
return json.Marshal(string(ret))
}
// PemBlockToString turns a pem.Block into the string encoded form.
func PemBlockToString(block *pem.Block) string {
if block.Bytes == nil || block.Type == "" {
return ""
}
return string(bytes.TrimSpace(pem.EncodeToMemory(block)))
}
var typeToName = map[int]string{
3: "CommonName",
5: "SerialNumber",
6: "Country",
7: "Locality",
8: "Province",
9: "StreetAddress",
10: "Organization",
11: "OrganizationalUnit",
17: "PostalCode",
}
type names []pkix.AttributeTypeAndValue
func (n names) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
for _, name := range n {
buf.WriteString(fmt.Sprintf("/%s=%s", typeToName[name.Type[3]], name.Value))
}
return json.Marshal(buf.String())
}
// MarshalJSON serialises the bundle to JSON. The resulting JSON
// structure contains the bundle (as a sequence of PEM-encoded
// certificates), the certificate, the private key, the size of they
// key, the issuer(s), the subject name(s), the expiration, the
// hostname(s), the OCSP server, and the signature on the certificate.
func (b *Bundle) MarshalJSON() ([]byte, error) {
if b == nil || b.Cert == nil {
return nil, errors.New("no certificate in bundle")
}
var keyBytes, rootBytes []byte
var keyLength int
var keyType, keyString string
keyLength = helpers.KeyLength(b.Cert.PublicKey)
switch b.Cert.PublicKeyAlgorithm {
case x509.ECDSA:
keyType = fmt.Sprintf("%d-bit ECDSA", keyLength)
case x509.RSA:
keyType = fmt.Sprintf("%d-bit RSA", keyLength)
case x509.DSA:
keyType = "DSA"
default:
keyType = "Unknown"
}
switch key := b.Key.(type) {
case *rsa.PrivateKey:
keyBytes = x509.MarshalPKCS1PrivateKey(key)
keyString = PemBlockToString(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes})
case *ecdsa.PrivateKey:
keyBytes, _ = x509.MarshalECPrivateKey(key)
keyString = PemBlockToString(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes})
case fmt.Stringer:
keyString = key.String()
}
if len(b.Hostnames) == 0 {
b.buildHostnames()
}
var ocspSupport = false
if b.Cert.OCSPServer != nil {
ocspSupport = true
}
var crlSupport = false
if b.Cert.CRLDistributionPoints != nil {
crlSupport = true
}
if b.Root != nil {
rootBytes = b.Root.Raw
}
return json.Marshal(map[string]interface{}{
"bundle": chain(b.Chain),
"root": PemBlockToString(&pem.Block{Type: "CERTIFICATE", Bytes: rootBytes}),
"crt": PemBlockToString(&pem.Block{Type: "CERTIFICATE", Bytes: b.Cert.Raw}),
"key": keyString,
"key_type": keyType,
"key_size": keyLength,
"issuer": names(b.Issuer.Names),
"subject": names(b.Subject.Names),
"expires": b.Expires,
"leaf_expires": b.LeafExpires,
"hostnames": b.Hostnames,
"ocsp_support": ocspSupport,
"crl_support": crlSupport,
"ocsp": b.Cert.OCSPServer,
"signature": helpers.SignatureString(b.Cert.SignatureAlgorithm),
"status": b.Status,
})
}
// buildHostnames sets bundle.Hostnames by the x509 cert's subject CN and DNS names
// Since the subject CN may overlap with one of the DNS names, it needs to handle
// the duplication by a set.
func (b *Bundle) buildHostnames() {
if b.Cert == nil {
return
}
// hset keeps a set of unique hostnames.
hset := make(map[string]bool)
// insert CN into hset
if b.Cert.Subject.CommonName != "" {
hset[b.Cert.Subject.CommonName] = true
}
// insert all DNS names into hset
for _, h := range b.Cert.DNSNames {
hset[h] = true
}
// convert hset to an array of hostnames
b.Hostnames = make([]string, len(hset))
i := 0
for h := range hset {
b.Hostnames[i] = h
i++
}
}

View File

@@ -0,0 +1,360 @@
package bundler
// This test file contains tests on checking the correctness of BundleFromFile and Bundle.
// We simulate various scenarios for Bundle and funnel the tests through BundleFromFile.
import (
"encoding/json"
"testing"
)
// A helper structure that defines a BundleFromFile test case.
type fileTest struct {
// PEM cert file to be bundled
cert string
// PEM private key file to be bundled
key string
// Root CA bundle
caBundleFile string
// Trust intermediate bundle
intBundleFile string
// Additional PEM intermediate certificates to be added into the bundler
extraIntermediates string
// Bundler creation function
bundlerConstructor func(*testing.T) (b *Bundler)
// Error checking function
errorCallback func(*testing.T, error)
// Bundle checking function
bundleChecking func(*testing.T, *Bundle)
}
/* ========== BundleFromFile Test Setup =============
For each pair of crypto algorithm X and key size Y, a CA chain is constructed:
Test_root_CA -> inter-L1 -> inter-L2--> cfssl-leaf-ecdsa256
|-> cfssl-leaf-ecdsa384
|-> cfssl-leaf-ecdsa521
|-> cfssl-leaf-rsa2048
|-> cfssl-leaf-rsa3072
|-> cfssl-leaf-rsa4096
Test_root_CA is a RSA cert, inter-L1 is RSA 4096 cert, inter-L2 is ecdsa-384 cert.
The max path length is set to be 1 for non-root CAs.
Two inter-* certs are assembled in intermediates.crt
There is also an expired L1 cert, sharing the same CSR with inter-L1. Also the
root CA processes the inter-L2 CSR directly to generate inter-L2-direct cert.
* Test_root_CA--> inter-L1-expired
|-> inter-L2-direct
Using inter-L2-direct as additional intermediate cert should shorten the
bundle chain.
*/
const (
leafECDSA256 = "testdata/cfssl-leaf-ecdsa256.pem"
leafECDSA384 = "testdata/cfssl-leaf-ecdsa384.pem"
leafECDSA521 = "testdata/cfssl-leaf-ecdsa521.pem"
leafRSA2048 = "testdata/cfssl-leaf-rsa2048.pem"
leafRSA3072 = "testdata/cfssl-leaf-rsa3072.pem"
leafRSA4096 = "testdata/cfssl-leaf-rsa4096.pem"
leafKeyECDSA256 = "testdata/cfssl-leaf-ecdsa256.key"
leafKeyECDSA384 = "testdata/cfssl-leaf-ecdsa384.key"
leafKeyECDSA521 = "testdata/cfssl-leaf-ecdsa521.key"
leafKeyRSA2048 = "testdata/cfssl-leaf-rsa2048.key"
leafKeyRSA3072 = "testdata/cfssl-leaf-rsa3072.key"
leafKeyRSA4096 = "testdata/cfssl-leaf-rsa4096.key"
leafletRSA4096 = "testdata/cfssl-leaflet-rsa4096.pem"
interL1 = "testdata/inter-L1.pem"
interL1Expired = "testdata/inter-L1-expired.pem"
interL1CSR = "testdata/inter-L1.csr"
interL2 = "testdata/inter-L2.pem"
interL2Direct = "testdata/inter-L2-direct.pem"
partialBundle = "testdata/partial-bundle.pem" // partialBundle is a partial cert chain {leaf-ecds256, inter-L2}
rpBundle = "testdata/reverse-partial-bundle.pem" // partialBundle is a partial cert chain in the reverse order {inter-L2, leaf-ecdsa256}
badBundle = "testdata/bad-bundle.pem" // badBundle is a non-verifying partial bundle {leaf-ecdsa256, leaf-ecdsa384}
interL2CSR = "testdata/inter-L2.csr"
certDSA2048 = "testdata/dsa2048.pem"
keyDSA2048 = "testdata/dsa2048.key"
)
// BundleFromFile test cases.
var fileTests = []fileTest{
// Input verification
{
cert: "not_such_cert.pem",
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: ExpectErrorMessage(`"code":1001`),
},
{
cert: emptyPEM,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: ExpectErrorMessage(`"code":1002`),
},
// Normal Keyless bundling for all supported public key types
{
cert: leafECDSA256,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: nil,
bundleChecking: ExpectBundleLength(3),
},
{
cert: leafECDSA384,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: nil,
bundleChecking: ExpectBundleLength(3),
},
{
cert: leafECDSA521,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: nil,
bundleChecking: ExpectBundleLength(3),
},
{
cert: leafRSA2048,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: nil,
bundleChecking: ExpectBundleLength(3),
},
{
cert: leafRSA3072,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: nil,
bundleChecking: ExpectBundleLength(3),
},
{
cert: leafRSA4096,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: nil,
bundleChecking: ExpectBundleLength(3),
},
// Normal bundling with private key for all supported key types
{
cert: leafECDSA256,
key: leafKeyECDSA256,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: nil,
bundleChecking: ExpectBundleLength(3),
},
{
cert: leafECDSA384,
key: leafKeyECDSA384,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: nil,
bundleChecking: ExpectBundleLength(3),
},
{
cert: leafECDSA521,
key: leafKeyECDSA521,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: nil,
bundleChecking: ExpectBundleLength(3),
},
{
cert: leafRSA2048,
key: leafKeyRSA2048,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: nil,
bundleChecking: ExpectBundleLength(3),
},
{
cert: leafRSA3072,
key: leafKeyRSA3072,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: nil,
bundleChecking: ExpectBundleLength(3),
},
{
cert: leafRSA4096,
key: leafKeyRSA4096,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: nil,
bundleChecking: ExpectBundleLength(3),
},
// Bundling with errors
// leaflet cert is signed by a leaf cert which is not included the intermediate bundle.
// So an UnknownAuthority error is expected.
{
cert: leafletRSA4096,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: ExpectErrorMessage(`"code":1220`),
},
// Expect TooManyIntermediates error because max path length is 1 for
// inter-L1 but the leaflet cert is 2 CA away from inter-L1.
{
cert: leafletRSA4096,
extraIntermediates: leafRSA4096,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: ExpectErrorMessage(`"code":1213`),
},
// Bundle with expired inter-L1 intermediate cert only, expect error 1211 VerifyFailed:Expired.
{
cert: interL2,
extraIntermediates: interL1Expired,
caBundleFile: testCFSSLRootBundle,
intBundleFile: emptyPEM,
errorCallback: ExpectErrorMessage(`"code":1211`),
},
// Bundle with private key mismatch
// RSA cert, ECC private key
{
cert: leafRSA4096,
key: leafKeyECDSA256,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: ExpectErrorMessages([]string{`"code":2300,`, `"message":"Private key does not match public key"`}),
},
// ECC cert, RSA private key
{
cert: leafECDSA256,
key: leafKeyRSA4096,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: ExpectErrorMessages([]string{`"code":2300,`, `"message":"Private key does not match public key"`}),
},
// RSA 2048 cert, RSA 4096 private key
{
cert: leafRSA2048,
key: leafKeyRSA4096,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: ExpectErrorMessages([]string{`"code":2300,`, `"message":"Private key does not match public key"`}),
},
// ECDSA 256 cert, ECDSA 384 private key
{
cert: leafECDSA256,
key: leafKeyECDSA384,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: ExpectErrorMessages([]string{`"code":2300,`, `"message":"Private key does not match public key"`}),
},
// DSA is NOT supported.
// Keyless bundling, expect private key error "NotRSAOrECC"
{
cert: certDSA2048,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: ExpectErrorMessages([]string{`"code":2200,`, `"message":"Private key algorithm is not RSA or ECC"`}),
},
// Bundling with DSA private key, expect error "Failed to parse private key"
{
cert: certDSA2048,
key: keyDSA2048,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: ExpectErrorMessages([]string{`"code":2003,`, `"message":"Failed to parse private key"`}),
},
// Bundle with partial chain less some intermediates, expected error 1220: UnknownAuthority
{
cert: badBundle,
caBundleFile: testCFSSLRootBundle,
intBundleFile: interL1,
errorCallback: ExpectErrorMessage(`"code":1220`),
},
// Bundle with misplaced key as cert
{
cert: leafKeyECDSA256,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: ExpectErrorMessages([]string{`"code":1003,`, `"message":"Failed to parse certificate"`}),
},
// Bundle with misplaced cert as key
{
cert: leafECDSA256,
key: leafECDSA256,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: ExpectErrorMessages([]string{`"code":2003,`, `"message":"Failed to parse private key"`}),
},
// Smart Bundling
// Bundling with a partial bundle should work the same as bundling the leaf.
{
cert: partialBundle,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: nil,
bundleChecking: ExpectBundleLength(3),
},
// Bundle with a partial bundle such that the intermediate provided in the
// partial bundle is verify by an intermediate. Yet itself is not in the intermediate
// pool. In such cases, the bundling should be able to store the new intermediate
// and return a correct bundle.
{
cert: partialBundle,
caBundleFile: testCFSSLRootBundle,
intBundleFile: interL1,
errorCallback: nil,
bundleChecking: ExpectBundleLength(3),
},
// Bundle with a reverse-ordered partial bundle.
// Bundler should be able to detect it and return a correct bundle.
{
cert: rpBundle,
caBundleFile: testCFSSLRootBundle,
intBundleFile: interL1,
errorCallback: nil,
bundleChecking: ExpectBundleLength(3),
},
// Bundle with a L2 cert direct signed by root, expect a shorter chain of length 2.
{
cert: leafECDSA256,
extraIntermediates: interL2Direct,
caBundleFile: testCFSSLRootBundle,
intBundleFile: testCFSSLIntBundle,
errorCallback: nil,
bundleChecking: ExpectBundleLength(2),
},
}
// TestBundleFromFile goes through test cases defined in fileTests. See below for test cases definition and details.
func TestBundleFromFile(t *testing.T) {
for _, test := range fileTests {
b := newCustomizedBundlerFromFile(t, test.caBundleFile, test.intBundleFile, test.extraIntermediates)
bundle, err := b.BundleFromFile(test.cert, test.key, Optimal, "")
if test.errorCallback != nil {
test.errorCallback(t, err)
} else {
if err != nil {
t.Fatalf("expected no error. but an error occurred: %v", err)
}
if test.bundleChecking != nil {
test.bundleChecking(t, bundle)
}
}
if bundle != nil {
bundle.Cert = nil
if _, err = json.Marshal(bundle); err == nil {
t.Fatal("bundle should fail with no cert")
}
}
}
}

View File

@@ -0,0 +1,339 @@
package bundler
// This test file contains tests on checking the correctness of BundleFromPEM
import (
"testing"
)
// A helper structure that defines a BundleFromPEM test case.
type pemTest struct {
// PEM cert to be bundled
cert []byte
// PEM private key to be bundled
key []byte
// PEM intermediate certificates to be considered when bundling
inters []byte
// Bundler creation function
bundlerConstructor func(*testing.T) (b *Bundler)
// Error checking function
errorCallback func(*testing.T, error)
// Bundle checking function
bundleChecking func(*testing.T, *Bundle)
}
// BundleFromPEM test cases.
var pemTests = []pemTest{
{
cert: GoDaddyIntermediateCert,
bundlerConstructor: newBundler,
errorCallback: nil,
bundleChecking: ExpectBundleLength(1),
},
{
cert: []byte(""),
bundlerConstructor: newBundler,
errorCallback: ExpectErrorMessage("\"code\":1002"),
},
{
cert: corruptCert,
bundlerConstructor: newBundler,
errorCallback: ExpectErrorMessage("\"code\":1002"),
},
{
cert: garbageCert,
bundlerConstructor: newBundler,
errorCallback: ExpectErrorMessage("\"code\":1003"),
},
{
cert: selfSignedCert,
bundlerConstructor: newBundler,
errorCallback: ExpectErrorMessage("\"code\":1100"),
},
// 121X errors are X509.CertificateInvalidError. This test
// covers the code path leads to all 121X errors.
{
cert: expiredCert,
bundlerConstructor: newBundler,
errorCallback: ExpectErrorMessage("\"code\":1211"),
},
// With a empty root cert pool, the valid root cert
// is seen as issued by an unknown authority.
{
cert: GoDaddyIntermediateCert,
bundlerConstructor: newBundlerWithoutRoots,
errorCallback: ExpectErrorMessage("\"code\":1220"),
},
}
// TestBundleFromPEM goes through the test cases defined in pemTests and run them through. See below for test case definitions.
func TestBundleFromPEM(t *testing.T) {
for i, test := range pemTests {
b := test.bundlerConstructor(t)
bundle, err := b.BundleFromPEMorDER(test.cert, test.key, Optimal, "")
if test.errorCallback != nil {
test.errorCallback(t, err)
} else {
if err != nil {
t.Errorf("expected no error. but an error occurred at %d-th test: %s", i, err.Error())
}
if test.bundleChecking != nil {
test.bundleChecking(t, bundle)
}
}
}
}
// GoDaddy intermediate cert valid until year 2034
var GoDaddyRootCert = []byte(`-----BEGIN CERTIFICATE-----
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
ReYNnyicsbkqWletNw+vHX/bvZ8=
-----END CERTIFICATE-----`)
// GoDaddy intermediate cert valid until year 2026
var GoDaddyIntermediateCert = []byte(`-----BEGIN CERTIFICATE-----
MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMx
ITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYw
MTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMH
QXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5j
b20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j
b20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3H
KrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQm
VZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpR
SgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRT
cDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ
6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEu
MB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDS
kdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEB
BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0f
BD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBv
c2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUH
AgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAO
BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IG
OgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMU
A2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o
0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTX
RE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuH
qDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWV
U+4=
-----END CERTIFICATE-----`)
// This is the same GoDaddy cert above except the last line is corrupted.
var corruptCert = []byte(`-----BEGIN CERTIFICATE-----
MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMx
ITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYw
MTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMH
QXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5j
b20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j
b20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3H
KrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQm
VZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpR
SgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRT
cDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ
6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEu
MB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDS
kdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEB
BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0f
BD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBv
c2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUH
AgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAO
BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IG
OgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMU
A2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o
0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTX
RE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuH
qDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWV
CORRUPTED
-----END CERTIFICATE-----`)
// A garbage cert, which can be decoded into ill-formed cert
var garbageCert = []byte(`-----BEGIN CERTIFICATE-----
MIICATCCAWoCCQDidF+uNJR6czANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB
cyBQdHkgTHRkMB4XDTEyMDUwMTIyNTUxN1oXDTEzMDUwMTIyNTUxN1owRTELMAkG
A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
nodhz31kLEJoeLSkRmrv8l7exkGtO0REtIbirj9BBy64ZXVBE7khKGO2cnM8U7yj
w7Ntfh+IvCjZVA3d2XqHS3Pjrt4HmU/cGCONE8+NEXoqdzLUDPOix1qDDRBvXs81
IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtpjl
KAV2qh6CYHZbdqixhDerjvJcD4Nsd7kExEZfHuECAwEAATANBgkqhkiG9w0BAQUF
AAOBgQCyOqs7+qpMrYCgL6OamDeCVojLoEp036PsnaYWf2NPmsVXdpYW40Foyyjp
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
iv5otkxO5rxtGPv7o2J1eMBpCuSkydvoz3Ey/QwGqbBwEXQ4xYCgra336gqW2KQt
+LnDCkE8f5oBhCIisExc2i8PDvsRsY70g/2gs983ImJjVR8sDw==
-----END CERTIFICATE-----`)
// A expired cert
var expiredCert = []byte(`-----BEGIN CERTIFICATE-----
MIIHlTCCBn2gAwIBAgIQCgFBPgAAATyS0MsAAAAAAjANBgkqhkiG9w0BAQUFADBY
MQswCQYDVQQGEwJVUzEXMBUGA1UECgwOSWRlblRydXN0IExMQy4xFzAVBgNVBAsM
DlRydXN0SUQgU2VydmVyMRcwFQYDVQQDDA5UcnVzdElEIENBIEE1MTAeFw0xMzAx
MzEyMjUzNDJaFw0xMzAyMDEyMjUzNDJaMIGIMSEwHwYDVQQDDBhleHBpcmVkLmlk
ZW50cnVzdHNzbC5jb20xFjAUBgNVBAsMDUlERU5UUlVTVCBJTkMxFjAUBgNVBAoM
DUlERU5UUlVTVCBJTkMxFzAVBgNVBAcMDlNBTFQgTEFLRSBDSVRZMQ0wCwYDVQQI
DARVVEFIMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAMfv32RRW//GXnNRiJAa7oMzKErpKb+AzWOqKaIw1kLwIcRfCZl6ZFVEHBLz
L0cAHg2WE5+jEXTlGC8Yitt294cUcuzM/UNR2/oTcviIbGSYyAl1NDbWUPIqrnpJ
K/QEMEcV3r2VSGHzgyhBBLrR51CapDYpy1beg7M4oi8535H4waxyTlvrx7YJ5ZnB
0SZNfmAv2UO2WObuFVQP5j+frotJ496ireXydhpseKxG/LZKiAQpHXRNMMu22rwz
Wun6wXTQ+rBNHaeLB00Pnd3GrTsdkg5KRqFVyh8+wa0a9Mvptj1PXMjiyb2TLdfd
fHmmxC1ueYfFf2bny4Bi/Urm7cMCAwEAAaOCBCgwggQkMA4GA1UdDwEB/wQEAwIF
oDCCAicGA1UdIASCAh4wggIaMIIBCwYKYIZIAYb5LwAGAzCB/DBABggrBgEFBQcC
ARY0aHR0cHM6Ly9zZWN1cmUuaWRlbnRydXN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9s
aWN5L3RzLzCBtwYIKwYBBQUHAgIwgaoagadUaGlzIFRydXN0SUQgU2VydmVyIENl
cnRpZmljYXRlIGhhcyBiZWVuIGlzc3VlZCBpbiBhY2NvcmRhbmNlIHdpdGggSWRl
blRydXN0J3MgVHJ1c3RJRCBDZXJ0aWZpY2F0ZSBQb2xpY3kgZm91bmQgYXQgaHR0
cHM6Ly9zZWN1cmUuaWRlbnRydXN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L3Rz
LzCCAQcGBmeBDAECAjCB/DBABggrBgEFBQcCARY0aHR0cHM6Ly9zZWN1cmUuaWRl
bnRydXN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L3RzLzCBtwYIKwYBBQUHAgIw
gaoagadUaGlzIFRydXN0SUQgU2VydmVyIENlcnRpZmljYXRlIGhhcyBiZWVuIGlz
c3VlZCBpbiBhY2NvcmRhbmNlIHdpdGggSWRlblRydXN0J3MgVHJ1c3RJRCBDZXJ0
aWZpY2F0ZSBQb2xpY3kgZm91bmQgYXQgaHR0cHM6Ly9zZWN1cmUuaWRlbnRydXN0
LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L3RzLzAdBgNVHQ4EFgQUcXFUZ6wayp6G
GAd+34JhzuVAv8MwgeYGA1UdHwSB3jCB2zA3oDWgM4YxaHR0cDovL2NybC5pZGVu
dHJ1c3QuY29tL3RydXN0aWQvdHJ1c3RpZGNhYTUxLmNybDCBn6CBnKCBmYaBlmxk
YXA6Ly9sZGFwLmlkZW50cnVzdC5jb20vY249VHJ1c3RJRCUyMFNlcnZlciUyMENB
JTIwQTUxLG91PVRydXN0SUQlMjBTZXJ2ZXIsbz1EaWdpdGFsJTIwU2lnbmF0dXJl
JTIwVHJ1c3QlMjBDby4sYz1VUz9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0O2Jp
bmFyeTB6BggrBgEFBQcBAQRuMGwwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLmlk
ZW50cnVzdC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9hcHBzLklkZW5UcnVzdC5j
b20vcm9vdHMvaWRlbnRydXN0dHJ1c3RpZHNzbC5wN2MwHQYDVR0lBBYwFAYIKwYB
BQUHAwEGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFBrSOtPUiKDLDl9SWcXcTo5OyAXW
MCMGA1UdEQQcMBqCGGV4cGlyZWQuaWRlbnRydXN0c3NsLmNvbTANBgkqhkiG9w0B
AQUFAAOCAQEArqD5svgbZCYiApfe5qAKXh96lRfJ1VWO335ZvHWsO6h/aIP2P9nt
CZ+wmP2M/EYOgteDgYIzJL1AAQbV/VNhX9kLcASWa5f63+sdmK9cotLwjkcSxCeb
a5bFyEpLQWgJLlMoHu2Kj2KHONbzXU2xN3E3dCVFbPVUBswCsPspoMr1JY9k1KLl
QDI8IWBbGiJvKcwifWXQ+g2l+NZjG4J0VZ/dwD9XsYqRBCb/1U/vCm+5H2sZ432H
JKwzKO81YBVCz/BVX3QWo4sPfTop3LEfVRVdDSBsXGmMqbLp2bTbKKuQ0fMF1g/o
vmsS6wZVxsSSna7VkmXdoLZDcf7Dv+yGEg==
-----END CERTIFICATE-----`)
// A self-signed cert
var selfSignedCert = []byte(`-----BEGIN CERTIFICATE-----
MIIERTCCAy2gAwIBAgIJAORAsvx6MZO7MA0GCSqGSIb3DQEBBQUAMHQxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEXMBUG
A1UEChMOQ2xvdWRGbGFyZSBMTEMxETAPBgNVBAsTCFNlY3VyaXR5MRQwEgYDVQQD
Ewt0ZXN0c3NsLmxvbDAeFw0xNDA0MDQyMjM4MzhaFw0yNDA0MDEyMjM4MzhaMHQx
CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
bzEXMBUGA1UEChMOQ2xvdWRGbGFyZSBMTEMxETAPBgNVBAsTCFNlY3VyaXR5MRQw
EgYDVQQDEwt0ZXN0c3NsLmxvbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAMKNLxpsnT37jSYkP9LVjw050Nmt1YMcEPwLe8zGUr8QzTWQ194Z9Ik/qYS0
UpQlx7+8UBoCanDTYuNKarHhmj4nZp+gc3mWWlaJKRnCJZ+Ru18x2lg9BzG4MwPQ
63ve0WxZ69/6J3lx53ertDgcD7S4v71BaeE10miBeJLK3JkV6fgGGfGRAGwU9vfm
OBbPTAw2SRdB1AaYTHaT4ANwUI7vvkIPrNuneTjOqlN9DAroUNIkXhV+fSmncRxi
RCAfP8/4BZdZ9C4TTKUpdAVUe1LUcHygK2f3YtOx8qJLCRMTRMYccSI1Y1idhX1s
SKIDDrOuELb+pGgno5PCe6i6MWcCAwEAAaOB2TCB1jAdBgNVHQ4EFgQUCkxuIVbR
+I8Z0A547Xj1R57ceXUwgaYGA1UdIwSBnjCBm4AUCkxuIVbR+I8Z0A547Xj1R57c
eXWheKR2MHQxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2Fu
IEZyYW5jaXNjbzEXMBUGA1UEChMOQ2xvdWRGbGFyZSBMTEMxETAPBgNVBAsTCFNl
Y3VyaXR5MRQwEgYDVQQDEwt0ZXN0c3NsLmxvbIIJAORAsvx6MZO7MAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKFaOjVRXCNsOznpZe0478mIFK6mNwwi
ZrLcrEUZ0FIOcPwsnQXd/HmrR4MVj3z3U62mE6qFo+07yJnnXdKBJ9ThjmNu6c4S
dk2xPbKTuACF7UhMgPlac0tEp/KSJTaMcjl23H+ol80LZ/t1113XSAZYHWsAgTjC
905kp66Gcq7c+GBgrBqR4e6Z2GYCeAk5aMy5f5s90teW2bIZE0hG1mFz1e25l9lI
SkAp0gZusX4yxqoSBqKmKXBkjrW5vkKJZjP51c7fuhfuAyNfxZF4Cz9SS0YSG8eh
H5kVbpLP+eSYMqF110qqjAo4tkgBquF6IppA+HQ66DN64+TeiXb3f2Y=
-----END CERTIFICATE-----`)
// An expired bundle
var expiredBundlePEM = []byte(`-----BEGIN CERTIFICATE-----
MIIEezCCAmOgAwIBAgIUUNg8o/9IQGSH6PBBxo06zIJEcaAwDQYJKoZIhvcNAQEN
BQAwgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJlMRwwGgYDVQQL
ExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMw
EQYDVQQIEwpDYWxpZm9ybmlhMR0wGwYDVQQDExRjbG91ZGZsYXJlLWludGVyLmNv
bTAeFw0xNzA1MjIyMjEzMDBaFw0yMjA1MjMwNDEzMDBaMIGLMQswCQYDVQQGEwJV
UzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzET
MBEGA1UEChMKQ2xvdWRGbGFyZTEcMBoGA1UECxMTU3lzdGVtcyBFbmdpbmVlcmlu
ZzEcMBoGA1UEAxMTY2xvdWRmbGFyZS1sZWFmLmNvbTBZMBMGByqGSM49AgEGCCqG
SM49AwEHA0IABIxG/fG9y/gjlAXvB77beERLbBooN98FGFAxVUA5IglylvgmfNxU
mI8mM2Uw9tzOLm9vORAraSSM4/6iSpCJreCjgZ4wgZswEwYDVR0lBAwwCgYIKwYB
BQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUqc1Wy6iYDBNjgFSn8WtaWXd3
yK4wHwYDVR0jBBgwFoAUiGC6GKR3uEEEG9Xvd1HCWxS6ID8wNgYDVR0RBC8wLYIT
Y2xvdWRmbGFyZS1sZWFmLmNvbYIWd3d3Y2xvdWRmbGFyZS1sZWFmLmNvbTANBgkq
hkiG9w0BAQ0FAAOCAgEAFH0prS82UElynKi7ik9gmTyrKz/YWTdXe9tGLPKSr88I
vBKMr1TuY13qTYKybTcUq3HTvJEvAyih7VbGQFqPnNpBE6DdE/M+Cfxo0iwM8e+C
49APOcMZKrddNnAfhmQqJuFJUCvHmKQ8p8VXfUm3q6qIwqwKTyubmGvO6b3JUtoD
MDOX4GZdYuIp7Q5jNWXDxw0k4ycaE8CZuRpOjVZ75X8OzwRG+ZFkAZlvxs4APpRr
n8gQ9U5cy00uLzwCgsNiJbweElcaBAEkurBZzLtXMd3sQKAH5kyqjeZTATb547tq
c0ykdcsf6ghBbGmNF+Bg3U7vY6ddep6sj5ifxCdAv9xqDCoKvG2JXYM2N56lFNWv
RJLIuB2nru/QeV0Rpq+UADfj3jkfxWk3H7YoOoYQTXllZ6aIfiOb9MqVYQUx1+G2
WV9sG10aRX+nYwvmddsUrkiKLgPhzE7Exn0f+dyN+FROsInniqNO7LC56ICJt72C
DPLLg0SQaaXjfEw3hnaf3YOHhP+opl9Q3HfVbe3ftJD1pp5BsK5S/hD/vegQZ3NL
MhBvsWX+HW3OuvGbbnLruqcRRH3LA/Pm+8IImAbdFXntcuuuQCb66YT/Gh0bkpoD
8R5sjAanl/EYiQIds4ZUuRrec+90ieceKM+P/b9wwLXHW2/o2s1evg2dGcJ97DY=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEfTCCA+agAwIBAgIUFalofeaKAEmnWkBXWu4lkwmmh3IwDQYJKoZIhvcNAQEF
BQAwfTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM
DVNhbiBGcmFuY2lzY28xEzARBgNVBAoMCkNsb3VkRmxhcmUxFDASBgNVBAsMC0RF
Vl9URVNUSU5HMRYwFAYDVQQDDA1DRlNTTF9URVNUX0NBMB4XDTE2MDIxODAxMTIw
MFoXDTE2MDIxODAxMTIwMVowgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxp
Zm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMwEQYDVQQKEwpDbG91ZEZs
YXJlMRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMR0wGwYDVQQDExRjbG91
ZGZsYXJlLWludGVyLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
AOUKdX6+PSxU/LxKocsCUj7HCc+FaDOPZV68Po3PVm7UF5DmbnLgJYJ/4aZEZM/v
5r8LnXQXDqumYicHQ2DHHBDasLTx8m0KeKOUYf9WMQ8gdjmVFoCiZwzxGDHok66/
0Glkkqmv2nJQxXncl5ZFta4sfmcQx3KT02l61LaBbG3j8PbRCWEr+0eRE6twuYRR
13AgZ3ATwnMjzxzvsW67qmAy0cq+XgYYfTK9vhPs+8J0fxXa0Iftu3yuhd30xLIV
XLu45GR+i6KnsSxVERSaVxjkS+lHXjUpdtmqI5CK6wn67vqYRRA2TzAJHX8Jb+KL
2/UEo5WNfAJ8S0heODQA8nHVU1JIfpegOlQRMv55DgnQUv1c1uwO5hqvv7MPQ3X/
m9Kjccs1FBH1/SVuzKyxYEQ34LErX3HI+6avbVnRtTR/UHkfnZVIXSrcjUm73BGj
33hrtiKl0ZyZnaUKGZPuvebOUFNiXemhTbqrfi/zAb1Tsm/h+xkn5EZ5sMj5NHdA
bpih3TqX2gRhnFZcFjtJM6zzC5O7eG5Kdqf8iladXTXtWxzrUPkb5CupzFl1dyS3
dqdkoIXvkmlScnu+6jBOaYeVvwogxr2Y69y4Zfg/qbPyBOLZquX9ovbuSP1DQmC/
/LV5t7YHHY/1MXr5U0MMvcn+9JWUV6ou3at4AgEqfK0vAgMBAAGjZjBkMA4GA1Ud
DwEB/wQEAwICBDASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBSIYLoYpHe4
QQQb1e93UcJbFLogPzAfBgNVHSMEGDAWgBS4Xu+uZ1C31vMH5Wq+VbNnOg2SPjAN
BgkqhkiG9w0BAQUFAAOBgQAnGnLG3r4g+0bLkeeh1Y71pL0Ui1LnvCA1v+yVDCd0
G9pj7cnHHXjnp4Pic6pP9uwxBAiUC6rzjpKrm1ULYMoQPLAYmwJiz+8yiE5vpMCA
Ov3LFPDNAbGF2wavwpCVolnVgHzPSFTEXN53DdXdVhcQ207P+zWNCNDF4Q33WSfm
Dw==
-----BEGIN CERTIFICATE-----
MIIEezCCAmOgAwIBAgIUUNg8o/9IQGSH6PBBxo06zIJEcaAwDQYJKoZIhvcNAQEN
BQAwgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJlMRwwGgYDVQQL
ExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMw
EQYDVQQIEwpDYWxpZm9ybmlhMR0wGwYDVQQDExRjbG91ZGZsYXJlLWludGVyLmNv
bTAeFw0xNzA1MjIyMjEzMDBaFw0yMjA1MjMwNDEzMDBaMIGLMQswCQYDVQQGEwJV
UzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzET
MBEGA1UEChMKQ2xvdWRGbGFyZTEcMBoGA1UECxMTU3lzdGVtcyBFbmdpbmVlcmlu
ZzEcMBoGA1UEAxMTY2xvdWRmbGFyZS1sZWFmLmNvbTBZMBMGByqGSM49AgEGCCqG
SM49AwEHA0IABIxG/fG9y/gjlAXvB77beERLbBooN98FGFAxVUA5IglylvgmfNxU
mI8mM2Uw9tzOLm9vORAraSSM4/6iSpCJreCjgZ4wgZswEwYDVR0lBAwwCgYIKwYB
BQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUqc1Wy6iYDBNjgFSn8WtaWXd3
yK4wHwYDVR0jBBgwFoAUiGC6GKR3uEEEG9Xvd1HCWxS6ID8wNgYDVR0RBC8wLYIT
Y2xvdWRmbGFyZS1sZWFmLmNvbYIWd3d3Y2xvdWRmbGFyZS1sZWFmLmNvbTANBgkq
hkiG9w0BAQ0FAAOCAgEAFH0prS82UElynKi7ik9gmTyrKz/YWTdXe9tGLPKSr88I
vBKMr1TuY13qTYKybTcUq3HTvJEvAyih7VbGQFqPnNpBE6DdE/M+Cfxo0iwM8e+C
49APOcMZKrddNnAfhmQqJuFJUCvHmKQ8p8VXfUm3q6qIwqwKTyubmGvO6b3JUtoD
MDOX4GZdYuIp7Q5jNWXDxw0k4ycaE8CZuRpOjVZ75X8OzwRG+ZFkAZlvxs4APpRr
n8gQ9U5cy00uLzwCgsNiJbweElcaBAEkurBZzLtXMd3sQKAH5kyqjeZTATb547tq
c0ykdcsf6ghBbGmNF+Bg3U7vY6ddep6sj5ifxCdAv9xqDCoKvG2JXYM2N56lFNWv
RJLIuB2nru/QeV0Rpq+UADfj3jkfxWk3H7YoOoYQTXllZ6aIfiOb9MqVYQUx1+G2
WV9sG10aRX+nYwvmddsUrkiKLgPhzE7Exn0f+dyN+FROsInniqNO7LC56ICJt72C
DPLLg0SQaaXjfEw3hnaf3YOHhP+opl9Q3HfVbe3ftJD1pp5BsK5S/hD/vegQZ3NL
MhBvsWX+HW3OuvGbbnLruqcRRH3LA/Pm+8IImAbdFXntcuuuQCb66YT/Gh0bkpoD
8R5sjAanl/EYiQIds4ZUuRrec+90ieceKM+P/b9wwLXHW2/o2s1evg2dGcJ97DY=
-----END CERTIFICATE-----`)

View File

@@ -0,0 +1,188 @@
package bundler
// This test file contains tests on checking the correctness of BundleFromRemote
import (
"flag"
"testing"
"github.com/cloudflare/cfssl/ubiquity"
)
var shouldTestSNI bool
func init() {
flag.BoolVar(&shouldTestSNI, "test-sni", false, "run the SNI tests")
flag.Parse()
}
// remoteTest defines a test case for BundleFromRemote. Hostname and ip are the test inputs.
// bundlerConstructor points the bundler ctor and errorCallback handles the error checking.
type remoteTest struct {
hostname string
ip string
bundlerConstructor func(*testing.T) (b *Bundler)
errorCallback func(*testing.T, error)
bundleCallback func(*testing.T, *Bundle)
}
const (
ValidSSLSite = "google.com"
SelfSignedSSLSite = "cacert.org"
MismatchedHostnameSite = "www.capitol.state.tx.us"
ECCCertSite = "benflare.us"
InvalidSite = "cloudflare1337.com"
ValidSNI = "alice.sni.velox.ch"
ValidSNIWildcard = "cloudflare.sni.velox.ch"
SNISANWildcard = "*.sni.velox.ch"
ValidSNIIP = "85.25.46.13"
InvalidIP = "300.300.300.300"
)
func getBundleHostnameChecker(hostname string) func(*testing.T, *Bundle) {
return func(t *testing.T, bundle *Bundle) {
if bundle == nil {
t.Fatalf("Nil bundle returned")
}
var found = false
for _, h := range bundle.Hostnames {
if h == hostname {
found = true
}
}
if !found {
t.Errorf("hostname expected but not found: %s", hostname)
}
}
}
// test cases of BundleFromRemote
var remoteTests = []remoteTest{
{
hostname: ValidSSLSite,
bundlerConstructor: newBundler,
errorCallback: nil,
},
{
hostname: SelfSignedSSLSite,
bundlerConstructor: newBundler,
errorCallback: ExpectErrorMessages([]string{`"code":12`}), // only check it is a 12xx error
},
{
hostname: MismatchedHostnameSite,
bundlerConstructor: newBundler,
errorCallback: ExpectErrorMessages([]string{`"code":12`}), // only check it is a 12xx error
},
{
hostname: InvalidSite,
bundlerConstructor: newBundler,
errorCallback: ExpectErrorMessages([]string{`"code":6000`, "dial tcp: lookup cloudflare1337.com"}),
},
{
hostname: InvalidIP,
bundlerConstructor: newBundler,
errorCallback: ExpectErrorMessages([]string{`"code":6000`, "dial tcp: lookup 300.300.300.300"}),
},
{
ip: InvalidIP,
bundlerConstructor: newBundler,
errorCallback: ExpectErrorMessages([]string{`"code":6000`, "dial tcp: lookup 300.300.300.300"}),
},
}
// TestBundleFromRemote goes through the test cases defined in remoteTests and run them through. See above for test case definitions.
func TestBundleFromRemote(t *testing.T) {
for _, bf := range []BundleFlavor{Ubiquitous, Optimal} {
for _, test := range remoteTests {
b := test.bundlerConstructor(t)
bundle, err := b.BundleFromRemote(test.hostname, test.ip, bf)
if test.errorCallback != nil {
test.errorCallback(t, err)
} else {
if err != nil {
t.Fatal("expected no error. but an error occurred", err.Error())
}
if test.bundleCallback != nil {
test.bundleCallback(t, bundle)
}
}
}
}
}
var remoteSNITests = []remoteTest{
{
hostname: ValidSNI,
bundlerConstructor: newBundler,
errorCallback: nil,
bundleCallback: getBundleHostnameChecker(ValidSNI),
},
{
hostname: ValidSNIWildcard,
bundlerConstructor: newBundler,
errorCallback: nil,
bundleCallback: getBundleHostnameChecker(SNISANWildcard),
},
{
hostname: ValidSNI,
ip: ValidSNIIP,
bundlerConstructor: newBundler,
errorCallback: nil,
bundleCallback: getBundleHostnameChecker(ValidSNI),
},
{
hostname: ValidSNIWildcard,
ip: ValidSNIIP,
bundlerConstructor: newBundler,
errorCallback: nil,
bundleCallback: getBundleHostnameChecker(SNISANWildcard),
},
}
// TestBundleFromRemoteSNI goes through the test cases defined in remoteSNITests and run them through. See above for test case definitions.
func TestBundleFromRemoteSNI(t *testing.T) {
if !shouldTestSNI {
t.Skip()
}
for _, bf := range []BundleFlavor{Ubiquitous, Optimal} {
for _, test := range remoteSNITests {
b := test.bundlerConstructor(t)
bundle, err := b.BundleFromRemote(test.hostname, test.ip, bf)
if test.errorCallback != nil {
test.errorCallback(t, err)
} else {
if err != nil {
t.Errorf("expected no error. but an error occurred: %s", err.Error())
}
if test.bundleCallback != nil {
test.bundleCallback(t, bundle)
}
}
}
}
}
func TestBundleFromRemoteFlavor(t *testing.T) {
b := newBundler(t)
ubiquity.Platforms = nil
ubiquity.LoadPlatforms(testMetadata)
bundle, err := b.BundleFromRemote(ECCCertSite, "", Ubiquitous)
if err != nil {
t.Fatalf("expected no error. but an error occurred: %s", err.Error())
}
if len(bundle.Chain) != 3 {
t.Error("expected 3-cert bundle. Got ", len(bundle.Chain))
}
if len(bundle.Status.Untrusted) != 0 {
t.Error("expected no untrusted platforms. Got ", bundle.Status.Untrusted)
}
bundle, err = b.BundleFromRemote(ECCCertSite, "", Optimal)
if err != nil {
t.Errorf("expected no error. but an error occurred: %s", err.Error())
}
if len(bundle.Chain) != 2 {
t.Error("expected 2-cert bundle. Got ", len(bundle.Chain))
}
}

820
vendor/github.com/cloudflare/cfssl/bundler/bundler.go generated vendored Normal file
View File

@@ -0,0 +1,820 @@
// Package bundler implements certificate bundling functionality for
// CFSSL.
package bundler
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
goerr "errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/ubiquity"
)
// IntermediateStash contains the path to the directory where
// downloaded intermediates should be saved.
// When unspecified, downloaded intermediates are not saved.
var IntermediateStash string
// BundleFlavor is named optimization strategy on certificate chain selection when bundling.
type BundleFlavor string
const (
// Optimal means the shortest chain with newest intermediates and
// the most advanced crypto.
Optimal BundleFlavor = "optimal"
// Ubiquitous is aimed to provide the chain which is accepted
// by the most platforms.
Ubiquitous BundleFlavor = "ubiquitous"
// Force means the bundler only verfiies the input as a valid bundle, not optimization is done.
Force BundleFlavor = "force"
)
const (
sha2Warning = "The bundle contains certificates signed with advanced hash functions such as SHA2, which are problematic for certain operating systems, e.g. Windows XP SP2."
ecdsaWarning = "The bundle contains ECDSA signatures, which are problematic for certain operating systems, e.g. Windows XP, Android 2.2 and Android 2.3."
expiringWarningStub = "The bundle is expiring within 30 days."
untrustedWarningStub = "The bundle may not be trusted by the following platform(s):"
ubiquityWarning = "Unable to measure bundle ubiquity: No platform metadata present."
)
// A Bundler contains the certificate pools for producing certificate
// bundles. It contains any intermediates and root certificates that
// should be used.
type Bundler struct {
RootPool *x509.CertPool
IntermediatePool *x509.CertPool
KnownIssuers map[string]bool
}
// NewBundler creates a new Bundler from the files passed in; these
// files should contain a list of valid root certificates and a list
// of valid intermediate certificates, respectively.
func NewBundler(caBundleFile, intBundleFile string) (*Bundler, error) {
var caBundle, intBundle []byte
var err error
if caBundleFile != "" {
log.Debug("Loading CA bundle: ", caBundleFile)
caBundle, err = ioutil.ReadFile(caBundleFile)
if err != nil {
log.Errorf("root bundle failed to load: %v", err)
return nil, errors.Wrap(errors.RootError, errors.ReadFailed, err)
}
}
if intBundleFile != "" {
log.Debug("Loading Intermediate bundle: ", intBundleFile)
intBundle, err = ioutil.ReadFile(intBundleFile)
if err != nil {
log.Errorf("intermediate bundle failed to load: %v", err)
return nil, errors.Wrap(errors.IntermediatesError, errors.ReadFailed, err)
}
}
if IntermediateStash != "" {
if _, err = os.Stat(IntermediateStash); err != nil && os.IsNotExist(err) {
log.Infof("intermediate stash directory %s doesn't exist, creating", IntermediateStash)
err = os.MkdirAll(IntermediateStash, 0755)
if err != nil {
log.Errorf("failed to create intermediate stash directory %s: %v",
IntermediateStash, err)
return nil, err
}
log.Infof("intermediate stash directory %s created", IntermediateStash)
}
}
return NewBundlerFromPEM(caBundle, intBundle)
}
// NewBundlerFromPEM creates a new Bundler from PEM-encoded root certificates and
// intermediate certificates.
// If caBundlePEM is nil, the resulting Bundler can only do "Force" bundle.
func NewBundlerFromPEM(caBundlePEM, intBundlePEM []byte) (*Bundler, error) {
log.Debug("parsing root certificates from PEM")
roots, err := helpers.ParseCertificatesPEM(caBundlePEM)
if err != nil {
log.Errorf("failed to parse root bundle: %v", err)
return nil, errors.New(errors.RootError, errors.ParseFailed)
}
log.Debug("parse intermediate certificates from PEM")
intermediates, err := helpers.ParseCertificatesPEM(intBundlePEM)
if err != nil {
log.Errorf("failed to parse intermediate bundle: %v", err)
return nil, errors.New(errors.IntermediatesError, errors.ParseFailed)
}
b := &Bundler{
KnownIssuers: map[string]bool{},
IntermediatePool: x509.NewCertPool(),
}
log.Debug("building certificate pools")
// RootPool will be nil if caBundlePEM is nil, also
// that translates to caBundleFile is "".
// Systems root store will be used.
if caBundlePEM != nil {
b.RootPool = x509.NewCertPool()
}
for _, c := range roots {
b.RootPool.AddCert(c)
b.KnownIssuers[string(c.Signature)] = true
}
for _, c := range intermediates {
b.IntermediatePool.AddCert(c)
b.KnownIssuers[string(c.Signature)] = true
}
log.Debug("bundler set up")
return b, nil
}
// VerifyOptions generates an x509 VerifyOptions structure that can be
// used for verifying certificates.
func (b *Bundler) VerifyOptions() x509.VerifyOptions {
return x509.VerifyOptions{
Roots: b.RootPool,
Intermediates: b.IntermediatePool,
KeyUsages: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageMicrosoftServerGatedCrypto,
x509.ExtKeyUsageNetscapeServerGatedCrypto,
},
}
}
// BundleFromFile takes a set of files containing the PEM-encoded leaf certificate
// (optionally along with some intermediate certs), the PEM-encoded private key
// and returns the bundle built from that key and the certificate(s).
func (b *Bundler) BundleFromFile(bundleFile, keyFile string, flavor BundleFlavor, password string) (*Bundle, error) {
log.Debug("Loading Certificate: ", bundleFile)
certsRaw, err := ioutil.ReadFile(bundleFile)
if err != nil {
return nil, errors.Wrap(errors.CertificateError, errors.ReadFailed, err)
}
var keyPEM []byte
// Load private key PEM only if a file is given
if keyFile != "" {
log.Debug("Loading private key: ", keyFile)
keyPEM, err = ioutil.ReadFile(keyFile)
if err != nil {
log.Debugf("failed to read private key: ", err)
return nil, errors.Wrap(errors.PrivateKeyError, errors.ReadFailed, err)
}
if len(keyPEM) == 0 {
log.Debug("key is empty")
return nil, errors.Wrap(errors.PrivateKeyError, errors.DecodeFailed, err)
}
}
return b.BundleFromPEMorDER(certsRaw, keyPEM, flavor, password)
}
// BundleFromPEMorDER builds a certificate bundle from the set of byte
// slices containing the PEM or DER-encoded certificate(s), private key.
func (b *Bundler) BundleFromPEMorDER(certsRaw, keyPEM []byte, flavor BundleFlavor, password string) (*Bundle, error) {
log.Debug("bundling from PEM files")
var key crypto.Signer
var err error
if len(keyPEM) != 0 {
key, err = helpers.ParsePrivateKeyPEM(keyPEM)
if err != nil {
log.Debugf("failed to parse private key: %v", err)
return nil, err
}
}
certs, err := helpers.ParseCertificatesPEM(certsRaw)
if err != nil {
// If PEM doesn't work try DER
var keyDER crypto.Signer
var errDER error
certs, keyDER, errDER = helpers.ParseCertificatesDER(certsRaw, password)
// Only use DER key if no key read from file
if key == nil && keyDER != nil {
key = keyDER
}
if errDER != nil {
log.Debugf("failed to parse certificates: %v", err)
// If neither parser works pass along PEM error
return nil, err
}
}
if len(certs) == 0 {
log.Debugf("no certificates found")
return nil, errors.New(errors.CertificateError, errors.DecodeFailed)
}
log.Debugf("bundle ready")
return b.Bundle(certs, key, flavor)
}
// BundleFromRemote fetches the certificate served by the server at
// serverName (or ip, if the ip argument is not the empty string). It
// is expected that the method will be able to make a connection at
// port 443. The certificate used by the server in this connection is
// used to build the bundle, which will necessarily be keyless.
func (b *Bundler) BundleFromRemote(serverName, ip string, flavor BundleFlavor) (*Bundle, error) {
config := &tls.Config{
RootCAs: b.RootPool,
ServerName: serverName,
}
// Dial by IP if present
var dialName string
if ip != "" {
dialName = ip + ":443"
} else {
dialName = serverName + ":443"
}
log.Debugf("bundling from remote %s", dialName)
dialer := &net.Dialer{Timeout: time.Duration(5) * time.Second}
conn, err := tls.DialWithDialer(dialer, "tcp", dialName, config)
var dialError string
// If there's an error in tls.Dial, try again with
// InsecureSkipVerify to fetch the remote bundle to (re-)bundle
// with. If the bundle is indeed not usable (expired, mismatched
// hostnames, etc.), report the error. Otherwise, create a
// working bundle and insert the tls error in the bundle.Status.
if err != nil {
log.Debugf("dial failed: %v", err)
// record the error msg
dialError = fmt.Sprintf("Failed rigid TLS handshake with %s: %v", dialName, err)
// dial again with InsecureSkipVerify
log.Debugf("try again with InsecureSkipVerify.")
config.InsecureSkipVerify = true
conn, err = tls.DialWithDialer(dialer, "tcp", dialName, config)
if err != nil {
log.Debugf("dial with InsecureSkipVerify failed: %v", err)
return nil, errors.Wrap(errors.DialError, errors.Unknown, err)
}
}
connState := conn.ConnectionState()
certs := connState.PeerCertificates
err = conn.VerifyHostname(serverName)
if err != nil {
log.Debugf("failed to verify hostname: %v", err)
return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err)
}
// Bundle with remote certs. Inject the initial dial error, if any, to the status reporting.
bundle, err := b.Bundle(certs, nil, flavor)
if err != nil {
return nil, err
} else if dialError != "" {
bundle.Status.Messages = append(bundle.Status.Messages, dialError)
}
return bundle, err
}
type fetchedIntermediate struct {
Cert *x509.Certificate
Name string
}
// fetchRemoteCertificate retrieves a single URL pointing to a certificate
// and attempts to first parse it as a DER-encoded certificate; if
// this fails, it attempts to decode it as a PEM-encoded certificate.
func fetchRemoteCertificate(certURL string) (fi *fetchedIntermediate, err error) {
log.Debugf("fetching remote certificate: %s", certURL)
var resp *http.Response
resp, err = http.Get(certURL)
if err != nil {
log.Debugf("failed HTTP get: %v", err)
return
}
defer resp.Body.Close()
var certData []byte
certData, err = ioutil.ReadAll(resp.Body)
if err != nil {
log.Debugf("failed to read response body: %v", err)
return
}
log.Debugf("attempting to parse certificate as DER")
crt, err := x509.ParseCertificate(certData)
if err != nil {
log.Debugf("attempting to parse certificate as PEM")
crt, err = helpers.ParseCertificatePEM(certData)
if err != nil {
log.Debugf("failed to parse certificate: %v", err)
return
}
}
log.Debugf("certificate fetch succeeds")
fi = &fetchedIntermediate{Cert: crt, Name: constructCertFileName(crt)}
return
}
func reverse(certs []*x509.Certificate) []*x509.Certificate {
n := len(certs)
if n == 0 {
return certs
}
rcerts := []*x509.Certificate{}
for i := n - 1; i >= 0; i-- {
rcerts = append(rcerts, certs[i])
}
return rcerts
}
// Check if the certs form a partial cert chain: every cert verifies
// the signature of the one in front of it.
func partialVerify(certs []*x509.Certificate) bool {
n := len(certs)
if n == 0 {
return false
}
for i := 0; i < n-1; i++ {
if certs[i].CheckSignatureFrom(certs[i+1]) != nil {
return false
}
}
return true
}
func isSelfSigned(cert *x509.Certificate) bool {
return cert.CheckSignatureFrom(cert) == nil
}
func isChainRootNode(cert *x509.Certificate) bool {
if isSelfSigned(cert) {
return true
}
return false
}
func (b *Bundler) verifyChain(chain []*fetchedIntermediate) bool {
// This process will verify if the root of the (partial) chain is in our root pool,
// and will fail otherwise.
log.Debugf("verifying chain")
for vchain := chain[:]; len(vchain) > 0; vchain = vchain[1:] {
cert := vchain[0]
// If this is a certificate in one of the pools, skip it.
if b.KnownIssuers[string(cert.Cert.Signature)] {
log.Debugf("certificate is known")
continue
}
_, err := cert.Cert.Verify(b.VerifyOptions())
if err != nil {
log.Debugf("certificate failed verification: %v", err)
return false
} else if len(chain) == len(vchain) && isChainRootNode(cert.Cert) {
// The first certificate in the chain is a root; it shouldn't be stored.
log.Debug("looking at root certificate, will not store")
continue
}
// leaf cert has an empty name, don't store leaf cert.
if cert.Name == "" {
continue
}
log.Debug("add certificate to intermediate pool:", cert.Name)
b.IntermediatePool.AddCert(cert.Cert)
b.KnownIssuers[string(cert.Cert.Signature)] = true
if IntermediateStash != "" {
fileName := filepath.Join(IntermediateStash, cert.Name)
var block = pem.Block{Type: "CERTIFICATE", Bytes: cert.Cert.Raw}
log.Debugf("write intermediate to stash directory: %s", fileName)
// If the write fails, verification should not fail.
err = ioutil.WriteFile(fileName, pem.EncodeToMemory(&block), 0644)
if err != nil {
log.Errorf("failed to write new intermediate: %v", err)
} else {
log.Info("stashed new intermediate ", cert.Name)
}
}
}
return true
}
// constructCertFileName returns a uniquely identifying file name for a certificate
func constructCertFileName(cert *x509.Certificate) string {
// construct the filename as the CN with no period and space
name := strings.Replace(cert.Subject.CommonName, ".", "", -1)
name = strings.Replace(name, " ", "", -1)
// add SKI and serial number as extra identifier
name += fmt.Sprintf("_%x", cert.SubjectKeyId)
name += fmt.Sprintf("_%x", cert.SerialNumber.Bytes())
name += ".crt"
return name
}
// fetchIntermediates goes through each of the URLs in the AIA "Issuing
// CA" extensions and fetches those certificates. If those
// certificates are not present in either the root pool or
// intermediate pool, the certificate is saved to file and added to
// the list of intermediates to be used for verification. This will
// not add any new certificates to the root pool; if the ultimate
// issuer is not trusted, fetching the certicate here will not change
// that.
func (b *Bundler) fetchIntermediates(certs []*x509.Certificate) (err error) {
if IntermediateStash != "" {
log.Debugf("searching intermediates")
if _, err := os.Stat(IntermediateStash); err != nil && os.IsNotExist(err) {
log.Infof("intermediate stash directory %s doesn't exist, creating", IntermediateStash)
err = os.MkdirAll(IntermediateStash, 0755)
if err != nil {
log.Errorf("failed to create intermediate stash directory %s: %v", IntermediateStash, err)
return err
}
log.Infof("intermediate stash directory %s created", IntermediateStash)
}
}
// stores URLs and certificate signatures that have been seen
seen := map[string]bool{}
var foundChains int
// Construct a verify chain as a reversed partial bundle,
// such that the certs are ordered by promxity to the root CAs.
var chain []*fetchedIntermediate
for i, cert := range certs {
var name string
// Only construct filenames for non-leaf intermediate certs
// so they will be saved to disk if necessary.
// Leaf cert gets a empty name and will be skipped.
if i > 0 {
name = constructCertFileName(cert)
}
chain = append([]*fetchedIntermediate{{cert, name}}, chain...)
seen[string(cert.Signature)] = true
}
// Verify the chain and store valid intermediates in the chain.
// If it doesn't verify, fetch the intermediates and extend the chain
// in a DFS manner and verify each time we hit a root.
for {
if len(chain) == 0 {
log.Debugf("search complete")
if foundChains == 0 {
return x509.UnknownAuthorityError{}
}
return nil
}
current := chain[0]
var advanced bool
if b.verifyChain(chain) {
foundChains++
}
log.Debugf("walk AIA issuers")
for _, url := range current.Cert.IssuingCertificateURL {
if seen[url] {
log.Debugf("url %s has been seen", url)
continue
}
crt, err := fetchRemoteCertificate(url)
if err != nil {
continue
} else if seen[string(crt.Cert.Signature)] {
log.Debugf("fetched certificate is known")
continue
}
seen[url] = true
seen[string(crt.Cert.Signature)] = true
chain = append([]*fetchedIntermediate{crt}, chain...)
advanced = true
break
}
if !advanced {
log.Debugf("didn't advance, stepping back")
chain = chain[1:]
}
}
}
// Bundle takes an X509 certificate (already in the
// Certificate structure), a private key as crypto.Signer in one of the appropriate
// formats (i.e. *rsa.PrivateKey or *ecdsa.PrivateKey, or even a opaque key), using them to
// build a certificate bundle.
func (b *Bundler) Bundle(certs []*x509.Certificate, key crypto.Signer, flavor BundleFlavor) (*Bundle, error) {
log.Infof("bundling certificate for %+v", certs[0].Subject)
if len(certs) == 0 {
return nil, nil
}
// Detect reverse ordering of the cert chain.
if len(certs) > 1 && !partialVerify(certs) {
rcerts := reverse(certs)
if partialVerify(rcerts) {
certs = rcerts
}
}
var ok bool
cert := certs[0]
if key != nil {
switch {
case cert.PublicKeyAlgorithm == x509.RSA:
var rsaPublicKey *rsa.PublicKey
if rsaPublicKey, ok = key.Public().(*rsa.PublicKey); !ok {
return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
}
if cert.PublicKey.(*rsa.PublicKey).N.Cmp(rsaPublicKey.N) != 0 {
return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
}
case cert.PublicKeyAlgorithm == x509.ECDSA:
var ecdsaPublicKey *ecdsa.PublicKey
if ecdsaPublicKey, ok = key.Public().(*ecdsa.PublicKey); !ok {
return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
}
if cert.PublicKey.(*ecdsa.PublicKey).X.Cmp(ecdsaPublicKey.X) != 0 {
return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch)
}
default:
return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECC)
}
} else {
switch {
case cert.PublicKeyAlgorithm == x509.RSA:
case cert.PublicKeyAlgorithm == x509.ECDSA:
default:
return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECC)
}
}
bundle := new(Bundle)
bundle.Cert = cert
bundle.Key = key
bundle.Issuer = &cert.Issuer
bundle.Subject = &cert.Subject
bundle.buildHostnames()
if flavor == Force {
// force bundle checks the certificates
// forms a verification chain.
if !partialVerify(certs) {
return nil,
errors.Wrap(errors.CertificateError, errors.VerifyFailed,
goerr.New("Unable to verify the certificate chain"))
}
bundle.Chain = certs
} else {
// disallow self-signed cert
if cert.CheckSignatureFrom(cert) == nil {
return nil, errors.New(errors.CertificateError, errors.SelfSigned)
}
chains, err := cert.Verify(b.VerifyOptions())
if err != nil {
log.Debugf("verification failed: %v", err)
// If the error was an unknown authority, try to fetch
// the intermediate specified in the AIA and add it to
// the intermediates bundle.
if _, ok := err.(x509.UnknownAuthorityError); !ok {
return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err)
}
log.Debugf("searching for intermediates via AIA issuer")
searchErr := b.fetchIntermediates(certs)
if searchErr != nil {
log.Debugf("search failed: %v", searchErr)
return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err)
}
log.Debugf("verifying new chain")
chains, err = cert.Verify(b.VerifyOptions())
if err != nil {
log.Debugf("failed to verify chain: %v", err)
return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err)
}
log.Debugf("verify ok")
}
var matchingChains [][]*x509.Certificate
switch flavor {
case Optimal:
matchingChains = optimalChains(chains)
case Ubiquitous:
if len(ubiquity.Platforms) == 0 {
log.Warning("No metadata, Ubiquitous falls back to Optimal.")
}
matchingChains = ubiquitousChains(chains)
default:
matchingChains = ubiquitousChains(chains)
}
bundle.Chain = matchingChains[0]
}
statusCode := int(errors.Success)
var messages []string
// Check if bundle is expiring.
expiringCerts := checkExpiringCerts(bundle.Chain)
if len(expiringCerts) > 0 {
statusCode |= errors.BundleExpiringBit
messages = append(messages, expirationWarning(expiringCerts))
}
// Check if bundle contains SHA2 certs.
if ubiquity.ChainHashUbiquity(bundle.Chain) <= ubiquity.SHA2Ubiquity {
statusCode |= errors.BundleNotUbiquitousBit
messages = append(messages, sha2Warning)
}
// Check if bundle contains ECDSA signatures.
if ubiquity.ChainKeyAlgoUbiquity(bundle.Chain) <= ubiquity.ECDSA256Ubiquity {
statusCode |= errors.BundleNotUbiquitousBit
messages = append(messages, ecdsaWarning)
}
// when forcing a bundle, bundle ubiquity doesn't matter
// also we don't retrieve the anchoring root of the bundle
var untrusted []string
if flavor != Force {
// Add root store presence info
root := bundle.Chain[len(bundle.Chain)-1]
bundle.Root = root
log.Infof("the anchoring root is %v", root.Subject)
// Check if there is any platform that doesn't trust the chain.
// Also, an warning will be generated if ubiquity.Platforms is nil,
untrusted = ubiquity.UntrustedPlatforms(root)
untrustedMsg := untrustedPlatformsWarning(untrusted)
if len(untrustedMsg) > 0 {
log.Debug("Populate untrusted platform warning.")
statusCode |= errors.BundleNotUbiquitousBit
messages = append(messages, untrustedMsg)
}
}
// Check if there is any platform that rejects the chain because of SHA1 deprecation.
sha1Msgs := ubiquity.SHA1DeprecationMessages(bundle.Chain)
if len(sha1Msgs) > 0 {
log.Debug("Populate SHA1 deprecation warning.")
statusCode |= errors.BundleNotUbiquitousBit
messages = append(messages, sha1Msgs...)
}
bundle.Status = &BundleStatus{ExpiringSKIs: getSKIs(bundle.Chain, expiringCerts), Code: statusCode, Messages: messages, Untrusted: untrusted}
// attempt to not to include the root certificate for optimization
if flavor != Force {
// Include at least one intermediate if the leaf has enabled OCSP and is not CA.
if bundle.Cert.OCSPServer != nil && !bundle.Cert.IsCA && len(bundle.Chain) <= 2 {
// No op. Return one intermediate if there is one.
} else {
// do not include the root.
bundle.Chain = bundle.Chain[:len(bundle.Chain)-1]
}
}
bundle.Status.IsRebundled = diff(bundle.Chain, certs)
bundle.Expires = helpers.ExpiryTime(bundle.Chain)
bundle.LeafExpires = bundle.Chain[0].NotAfter
log.Debugf("bundle complete")
return bundle, nil
}
// checkExpiringCerts returns indices of certs that are expiring within 30 days.
func checkExpiringCerts(chain []*x509.Certificate) (expiringIntermediates []int) {
now := time.Now()
for i, cert := range chain {
if cert.NotAfter.Sub(now).Hours() < 720 {
expiringIntermediates = append(expiringIntermediates, i)
}
}
return
}
// getSKIs returns a list of cert subject key id in the bundle chain with matched indices.
func getSKIs(chain []*x509.Certificate, indices []int) (skis []string) {
for _, index := range indices {
ski := fmt.Sprintf("%X", chain[index].SubjectKeyId)
skis = append(skis, ski)
}
return
}
// expirationWarning generates a warning message with expiring certs.
func expirationWarning(expiringIntermediates []int) (ret string) {
if len(expiringIntermediates) == 0 {
return
}
ret = expiringWarningStub
if len(expiringIntermediates) > 1 {
ret = ret + "The expiring certs are"
} else {
ret = ret + "The expiring cert is"
}
for _, index := range expiringIntermediates {
ret = ret + " #" + strconv.Itoa(index+1)
}
ret = ret + " in the chain."
return
}
// untrustedPlatformsWarning generates a warning message with untrusted platform names.
func untrustedPlatformsWarning(platforms []string) string {
if len(ubiquity.Platforms) == 0 {
return ubiquityWarning
}
if len(platforms) == 0 {
return ""
}
msg := untrustedWarningStub
for i, platform := range platforms {
if i > 0 {
msg += ","
}
msg += " " + platform
}
msg += "."
return msg
}
// Optimal chains are the shortest chains, with newest intermediates and most advanced crypto suite being the tie breaker.
func optimalChains(chains [][]*x509.Certificate) [][]*x509.Certificate {
// Find shortest chains
chains = ubiquity.Filter(chains, ubiquity.CompareChainLength)
// Find the chains with longest expiry.
chains = ubiquity.Filter(chains, ubiquity.CompareChainExpiry)
// Find the chains with more advanced crypto suite
chains = ubiquity.Filter(chains, ubiquity.CompareChainCryptoSuite)
return chains
}
// Ubiquitous chains are the chains with highest platform coverage and break ties with the optimal strategy.
func ubiquitousChains(chains [][]*x509.Certificate) [][]*x509.Certificate {
// Filter out chains with highest cross platform ubiquity.
chains = ubiquity.Filter(chains, ubiquity.ComparePlatformUbiquity)
// Prefer that all intermediates are SHA-2 certs if the leaf is a SHA-2 cert, in order to improve ubiquity.
chains = ubiquity.Filter(chains, ubiquity.CompareSHA2Homogeneity)
// Filter shortest chains
chains = ubiquity.Filter(chains, ubiquity.CompareChainLength)
// Filter chains with highest signature hash ubiquity.
chains = ubiquity.Filter(chains, ubiquity.CompareChainHashUbiquity)
// Filter chains with highest keyAlgo ubiquity.
chains = ubiquity.Filter(chains, ubiquity.CompareChainKeyAlgoUbiquity)
// Filter chains with intermediates that last longer.
chains = ubiquity.Filter(chains, ubiquity.CompareExpiryUbiquity)
// Use the optimal strategy as final tie breaker.
return optimalChains(chains)
}
// diff checkes if two input cert chains are not identical
func diff(chain1, chain2 []*x509.Certificate) bool {
// Check if bundled one is different from the input.
diff := false
if len(chain1) != len(chain2) {
diff = true
} else {
for i := 0; i < len(chain1); i++ {
cert1 := chain1[i]
cert2 := chain2[i]
// Use signature to differentiate.
if !bytes.Equal(cert1.Signature, cert2.Signature) {
diff = true
break
}
}
}
return diff
}

View File

@@ -0,0 +1,183 @@
package bundler
// This test file contains tests on checking Bundle.Status with SHA-1 deprecation warning.
import (
"crypto/x509"
"io/ioutil"
"testing"
"time"
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/signer"
"github.com/cloudflare/cfssl/signer/local"
"github.com/cloudflare/cfssl/ubiquity"
)
const (
sha1CA = "testdata/ca.pem"
sha1CAKey = "testdata/ca.key"
sha1Intermediate = "testdata/inter-L1-sha1.pem"
sha2Intermediate = "testdata/inter-L1.pem"
intermediateKey = "testdata/inter-L1.key"
intermediateCSR = "testdata/inter-L1.csr"
leafCSR = "testdata/cfssl-leaf-ecdsa256.csr"
)
func TestChromeWarning(t *testing.T) {
b := newCustomizedBundlerFromFile(t, sha1CA, sha1Intermediate, "")
s, err := local.NewSignerFromFile(sha1Intermediate, intermediateKey, nil)
if err != nil {
t.Fatal(err)
}
csrBytes, err := ioutil.ReadFile(leafCSR)
if err != nil {
t.Fatal(err)
}
signingRequest := signer.SignRequest{Request: string(csrBytes)}
certBytes, err := s.Sign(signingRequest)
if err != nil {
t.Fatal(err)
}
// Bundle a leaf cert with default 1 year expiration
bundle, err := b.BundleFromPEMorDER(certBytes, nil, Ubiquitous, "")
if err != nil {
t.Fatal("bundling failed: ", err)
}
// should be not ubiquitous due to SHA2 and ECDSA support issues in legacy platforms
if bundle.Status.Code&errors.BundleNotUbiquitousBit != errors.BundleNotUbiquitousBit {
t.Fatal("Incorrect bundle status code. Bundle status code:", bundle.Status.Code)
}
fullChain := append(bundle.Chain, bundle.Root)
sha1Msgs := ubiquity.SHA1DeprecationMessages(fullChain)
// Since the new SHA-1 cert is expired after 2015, it definitely trigger Chrome's deprecation policies.
if len(sha1Msgs) == 0 {
t.Fatal("SHA1 Deprecation Message should not be empty")
}
// check SHA1 deprecation warnings
var sha1MsgNotFound bool
for _, sha1Msg := range sha1Msgs {
foundMsg := false
for _, message := range bundle.Status.Messages {
if message == sha1Msg {
foundMsg = true
}
}
if !foundMsg {
sha1MsgNotFound = true
break
}
}
if sha1MsgNotFound {
t.Fatalf("Incorrect bundle status messages. Bundle status messages:%v, expected to contain: %v\n", bundle.Status.Messages, sha1Msgs)
}
}
func TestSHA2Preferences(t *testing.T) {
// create a CA signer and signs a new intermediate with SHA-1
sha1CASigner := makeCASignerFromFile(sha1CA, sha1CAKey, x509.SHA1WithRSA, t)
// create a CA signer and signs a new intermediate with SHA-2
sha2CASigner := makeCASignerFromFile(sha1CA, sha1CAKey, x509.SHA256WithRSA, t)
// sign two different intermediates
sha1InterBytes := signCSRFile(sha1CASigner, intermediateCSR, t)
sha2InterBytes := signCSRFile(sha2CASigner, intermediateCSR, t)
interKeyBytes, err := ioutil.ReadFile(intermediateKey)
if err != nil {
t.Fatal(err)
}
// create a intermediate signer from SHA-1 intermediate cert/key
sha2InterSigner := makeCASigner(sha1InterBytes, interKeyBytes, x509.SHA256WithRSA, t)
// sign a leaf cert
leafBytes := signCSRFile(sha2InterSigner, leafCSR, t)
// create a bundler with SHA-1 and SHA-2 intermediate certs of same key.
b := newCustomizedBundlerFromFile(t, sha1CA, sha1Intermediate, "")
if err != nil {
t.Fatal(err)
}
sha1Inter, _ := helpers.ParseCertificatePEM(sha1InterBytes)
sha2Inter, _ := helpers.ParseCertificatePEM(sha2InterBytes)
b.IntermediatePool.AddCert(sha1Inter)
b.IntermediatePool.AddCert(sha2Inter)
bundle, err := b.BundleFromPEMorDER(leafBytes, nil, Ubiquitous, "")
if err != nil {
t.Fatal("bundling failed: ", err)
}
if bundle.Chain[1].SignatureAlgorithm != x509.SHA256WithRSA {
t.Fatal("ubiquity selection by SHA-2 homogenity failed.")
}
}
func makeCASignerFromFile(certFile, keyFile string, sigAlgo x509.SignatureAlgorithm, t *testing.T) signer.Signer {
certBytes, err := ioutil.ReadFile(certFile)
if err != nil {
t.Fatal(err)
}
keyBytes, err := ioutil.ReadFile(keyFile)
if err != nil {
t.Fatal(err)
}
return makeCASigner(certBytes, keyBytes, sigAlgo, t)
}
func makeCASigner(certBytes, keyBytes []byte, sigAlgo x509.SignatureAlgorithm, t *testing.T) signer.Signer {
cert, err := helpers.ParseCertificatePEM(certBytes)
if err != nil {
t.Fatal(err)
}
key, err := helpers.ParsePrivateKeyPEM(keyBytes)
if err != nil {
t.Fatal(err)
}
defaultProfile := &config.SigningProfile{
Usage: []string{"cert sign"},
CAConstraint: config.CAConstraint{IsCA: true},
Expiry: time.Hour,
ExpiryString: "1h",
}
policy := &config.Signing{
Profiles: map[string]*config.SigningProfile{},
Default: defaultProfile,
}
s, err := local.NewSigner(key, cert, sigAlgo, policy)
if err != nil {
t.Fatal(err)
}
return s
}
func signCSRFile(s signer.Signer, csrFile string, t *testing.T) []byte {
csrBytes, err := ioutil.ReadFile(csrFile)
if err != nil {
t.Fatal(err)
}
signingRequest := signer.SignRequest{Request: string(csrBytes)}
certBytes, err := s.Sign(signingRequest)
if err != nil {
t.Fatal(err)
}
return certBytes
}

View File

@@ -0,0 +1,924 @@
package bundler
// This test file contains mostly tests on checking Bundle.Status when bundling under different circumstances.
import (
"bytes"
"crypto/x509"
"encoding/json"
"io/ioutil"
"strings"
"testing"
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/ubiquity"
)
const (
testCaBundle = "testdata/ca-bundle.pem"
testIntCaBundle = "testdata/int-bundle.pem"
testNSSRootBundle = "testdata/nss.pem"
testMetadata = "testdata/ca-bundle.crt.metadata"
testCFSSLRootBundle = "testdata/ca.pem"
testCAFile = "testdata/ca.pem"
testCAKeyFile = "testdata/ca.key"
testCFSSLIntBundle = "testdata/intermediates.crt"
emptyPEM = "testdata/empty.pem"
interL1SHA1 = "testdata/inter-L1-sha1.pem"
interL1Key = "testdata/inter-L1.key"
interL2SHA2 = "testdata/inter-L2.pem"
interL2Key = "testdata/inter-L2.key"
)
// Simply create a bundler
func TestNewBundler(t *testing.T) {
newBundler(t)
}
func TestNewBundlerMissingCA(t *testing.T) {
badFile := "testdata/no_such_file.pem"
_, err := NewBundler(badFile, testIntCaBundle)
if err == nil {
t.Fatal("Should fail with error code 4001")
}
// generate a function checking error content
errorCheck := ExpectErrorMessage(`"code":4001`)
errorCheck(t, err)
}
func TestNewBundlerMissingIntermediate(t *testing.T) {
badFile := "testdata/no_such_file.pem"
_, err := NewBundler(testCaBundle, badFile)
if err == nil {
t.Fatal("Should fail with error code 3001")
}
// generate a function checking error content
errorCheck := ExpectErrorMessage(`"code":3001`)
errorCheck(t, err)
}
// JSON object of a bundle
type bundleObject struct {
Bundle string `json:"bundle"`
Root string `json:"root"`
Cert string `json:"crt"`
Key string `json:"key"`
KeyType string `json:"key_type"`
KeySize int `json:"key_size"`
Issuer string `json:"issuer"`
Subject string `json:"subject"`
Expires string `json:"expires"`
Hostnames []string `json:"hostnames"`
OCSPSupport bool `json:"ocsp_support"`
CRLSupport bool `json:"crl_support"`
OCSP []string `json:"ocsp"`
Signature string `json:"signature"`
Status BundleStatus
}
var godaddyIssuerString = `/Country=US/Organization=The Go Daddy Group, Inc./OrganizationalUnit=Go Daddy Class 2 Certification Authority`
var godaddySubjectString = `/Country=US/Province=Arizona/Locality=Scottsdale/Organization=GoDaddy.com, Inc./OrganizationalUnit=http://certificates.godaddy.com/repository/CommonName=Go Daddy Secure Certification Authority/SerialNumber=07969287`
// Test marshal to JSON
// Also serves as a JSON format regression test.
func TestBundleMarshalJSON(t *testing.T) {
b := newBundler(t)
bundle, _ := b.BundleFromPEMorDER(GoDaddyIntermediateCert, nil, Optimal, "")
bytes, err := json.Marshal(bundle)
if err != nil {
t.Fatal(err)
}
var obj bundleObject
err = json.Unmarshal(bytes, &obj)
if err != nil {
t.Fatal(err)
}
if obj.Bundle == "" {
t.Fatal("bundle is empty.")
}
if obj.Bundle != string(GoDaddyIntermediateCert) {
t.Fatal("bundle is incorrect:", obj.Bundle)
}
if obj.Key != "" {
t.Fatal("key is not empty:", obj.Key)
}
if obj.Root != string(GoDaddyRootCert) {
t.Fatal("Root is not recovered")
}
if obj.Cert != string(GoDaddyIntermediateCert) {
t.Fatal("Cert is not recovered")
}
if obj.KeyType != "2048-bit RSA" {
t.Fatal("Incorrect key type:", obj.KeyType)
}
if obj.KeySize != 2048 {
t.Fatal("Incorrect key size:", obj.KeySize)
}
if obj.Issuer != godaddyIssuerString {
t.Fatal("Incorrect issuer:", obj.Issuer)
}
if obj.Subject != godaddySubjectString {
t.Fatal("Incorrect subject:", obj.Subject)
}
if obj.Expires != "2026-11-16T01:54:37Z" {
t.Fatal("Incorrect expiration time:", obj.Expires)
}
if len(obj.Hostnames) != 1 || obj.Hostnames[0] != "Go Daddy Secure Certification Authority" {
t.Fatal("Incorrect hostnames:", obj.Hostnames)
}
if obj.OCSPSupport != true {
t.Fatal("Incorrect OCSP support flag:", obj.OCSPSupport)
}
if obj.CRLSupport != true {
t.Fatal("Incorrect CRL support flag:", obj.CRLSupport)
}
if len(obj.OCSP) != 1 || obj.OCSP[0] != `http://ocsp.godaddy.com` {
t.Fatal("Incorrect ocsp server list:", obj.OCSP)
}
if obj.Signature != "SHA1WithRSA" {
t.Fatal("Incorrect cert signature method:", obj.Signature)
}
}
func TestBundleWithECDSAKeyMarshalJSON(t *testing.T) {
b := newCustomizedBundlerFromFile(t, testCFSSLRootBundle, testCFSSLIntBundle, "")
bundle, _ := b.BundleFromFile(leafECDSA256, leafKeyECDSA256, Optimal, "")
jsonBytes, err := json.Marshal(bundle)
if err != nil {
t.Fatal(err)
}
var obj map[string]interface{}
err = json.Unmarshal(jsonBytes, &obj)
if err != nil {
t.Fatal(err)
}
key := obj["key"].(string)
keyBytes, _ := ioutil.ReadFile(leafKeyECDSA256)
keyBytes = bytes.Trim(keyBytes, " \n")
if key != string(keyBytes) {
t.Fatal("key is not recovered.")
}
cert := obj["crt"].(string)
certBytes, _ := ioutil.ReadFile(leafECDSA256)
certBytes = bytes.Trim(certBytes, " \n")
if cert != string(certBytes) {
t.Fatal("cert is not recovered.")
}
keyType := obj["key_type"]
if keyType != "256-bit ECDSA" {
t.Fatal("Incorrect key type:", keyType)
}
}
func TestBundleWithRSAKeyMarshalJSON(t *testing.T) {
b := newCustomizedBundlerFromFile(t, testCFSSLRootBundle, testCFSSLIntBundle, "")
bundle, _ := b.BundleFromFile(leafRSA2048, leafKeyRSA2048, Optimal, "")
jsonBytes, err := json.Marshal(bundle)
if err != nil {
t.Fatal(err)
}
var obj map[string]interface{}
err = json.Unmarshal(jsonBytes, &obj)
if err != nil {
t.Fatal(err)
}
key := obj["key"].(string)
keyBytes, _ := ioutil.ReadFile(leafKeyRSA2048)
keyBytes = bytes.Trim(keyBytes, " \n")
if key != string(keyBytes) {
t.Error("key is", key)
t.Error("keyBytes is", string(keyBytes))
t.Fatal("key is not recovered.")
}
cert := obj["crt"].(string)
certBytes, _ := ioutil.ReadFile(leafRSA2048)
certBytes = bytes.Trim(certBytes, " \n")
if cert != string(certBytes) {
t.Fatal("cert is not recovered.")
}
keyType := obj["key_type"]
if keyType != "2048-bit RSA" {
t.Fatal("Incorrect key type:", keyType)
}
}
// Test marshal to JSON on hostnames
func TestBundleHostnamesMarshalJSON(t *testing.T) {
b := newBundler(t)
bundle, _ := b.BundleFromPEMorDER(GoDaddyIntermediateCert, nil, Optimal, "")
expected := []byte(`["Go Daddy Secure Certification Authority"]`)
hostnames, _ := json.Marshal(bundle.Hostnames)
if !bytes.Equal(hostnames, expected) {
t.Fatal("Hostnames construction failed for godaddy root cert.", string(hostnames))
}
}
// Tests on verifying the rebundle flag and error code in Bundle.Status when rebundling.
func TestRebundleFromPEM(t *testing.T) {
newBundler := newCustomizedBundlerFromFile(t, testCFSSLRootBundle, interL1, "")
newBundle, err := newBundler.BundleFromPEMorDER(expiredBundlePEM, nil, Optimal, "")
if err != nil {
t.Fatalf("Re-bundle failed. %s", err.Error())
}
newChain := newBundle.Chain
if len(newChain) != 2 {
t.Fatalf("Expected bundle chain length is 2. Got %d.", len(newChain))
}
expiredChain, _ := helpers.ParseCertificatesPEM(expiredBundlePEM)
for i, cert := range newChain {
old := expiredChain[i]
if i == 0 {
if !bytes.Equal(old.Signature, cert.Signature) {
t.Fatal("Leaf cert should be the same.")
}
} else {
if bytes.Equal(old.Signature, cert.Signature) {
t.Fatal("Intermediate cert should be different.")
}
}
}
// The status must be {Code: ExpiringBit is not set, IsRebundled:true, ExpiringSKIs:{}}
if len(newBundle.Status.ExpiringSKIs) != 0 || !newBundle.Status.IsRebundled || newBundle.Status.Code&errors.BundleExpiringBit != 0 {
t.Fatal("Rebundle Status is incorrect.")
}
}
// Test on verifying ubiquitous messaging in Bundle.Status.
func TestUbiquitousBundle(t *testing.T) {
L1Cert := readCert(interL1)
// Simulate the case that L1Cert is added to trust store by one platform but not yet in another.
b := newCustomizedBundlerFromFile(t, testCFSSLRootBundle, testCFSSLIntBundle, "")
b.RootPool.AddCert(L1Cert)
// Prepare Platforms.
platformA := ubiquity.Platform{Name: "MacroSoft", Weight: 100, HashAlgo: "SHA2", KeyAlgo: "ECDSA256", KeyStoreFile: testCFSSLRootBundle}
platformA.ParseAndLoad()
platformB := ubiquity.Platform{Name: "Godzilla", Weight: 100, HashAlgo: "SHA2", KeyAlgo: "ECDSA256", KeyStoreFile: testCFSSLRootBundle}
platformB.ParseAndLoad()
platformA.KeyStore.Add(L1Cert)
ubiquity.Platforms = []ubiquity.Platform{platformA, platformB}
// Optimal bundle algorithm will picks up the new root and shorten the chain.
optimalBundle, err := b.BundleFromFile(leafECDSA256, "", Optimal, "")
if err != nil {
t.Fatal("Optimal bundle failed:", err)
}
if len(optimalBundle.Chain) != 2 {
t.Fatal("Optimal bundle failed the chain length test. Chain length:", len(optimalBundle.Chain))
}
// The only trust platform is "Macrosoft".
if len(optimalBundle.Status.Untrusted) != 1 {
t.Fatal("Optimal bundle status has incorrect untrusted platforms", optimalBundle.Status.Untrusted)
}
checkUbiquityWarningAndCode(t, optimalBundle, true)
// Ubiquitous bundle will remain the same.
ubiquitousBundle, err := b.BundleFromFile(leafECDSA256, "", Ubiquitous, "")
if err != nil {
t.Fatal("Ubiquitous bundle failed")
}
if len(ubiquitousBundle.Chain) != 3 {
t.Fatal("Ubiquitous bundle failed")
}
// Should be trusted by both platforms.
if len(ubiquitousBundle.Status.Untrusted) != 0 {
t.Fatal("Ubiquitous bundle status has incorrect untrusted platforms", len(ubiquitousBundle.Status.Untrusted))
}
checkUbiquityWarningAndCode(t, ubiquitousBundle, false)
}
func TestUbiquityBundleWithoutMetadata(t *testing.T) {
b := newCustomizedBundlerFromFile(t, testCFSSLRootBundle, testCFSSLIntBundle, "")
L1Cert := readCert(interL1)
b.RootPool.AddCert(L1Cert)
// Without platform info, ubiquitous bundling falls back to optimal bundling.
ubiquity.Platforms = nil
nuBundle, err := b.BundleFromFile(leafECDSA256, "", Ubiquitous, "")
if err != nil {
t.Fatal("Ubiquitous-fall-back-to-optimal bundle failed: ", err)
}
if len(nuBundle.Chain) != 2 {
t.Fatal("Ubiquitous-fall-back-to-optimal bundle failed")
}
// Should be trusted by all (i.e. zero) platforms.
if len(nuBundle.Status.Untrusted) != 0 {
t.Fatal("Ubiquitous-fall-back-to-optimal bundle status has incorrect untrusted platforms", len(nuBundle.Status.Untrusted))
}
checkUbiquityWarningAndCode(t, nuBundle, true)
}
func checkUbiquityWarningAndCode(t *testing.T, bundle *Bundle, expected bool) {
found := false
for _, msg := range bundle.Status.Messages {
if strings.Contains(msg, untrustedWarningStub) || strings.Contains(msg, ubiquityWarning) {
found = true
}
}
if found != expected {
t.Fatal("Expected ubiquity warning: ", expected, " Found ubiquity warning:", found)
}
// check status code
if expected && bundle.Status.Code&errors.BundleNotUbiquitousBit == 0 {
t.Fatal("Bundle status doesn't set BundleNotUbiquitousBit :", bundle.Status.Code)
}
}
// Regression test on bundle with all flavors:
// Ubiquitous bundle optimizes bundle length given the platform ubiquity is the same; Force bundle
// with return the same bundle; Optimal bundle always chooses shortest bundle length.
func TestForceBundle(t *testing.T) {
// create a CA signer and signs a new intermediate with SHA-2
caSigner := makeCASignerFromFile(testCAFile, testCAKeyFile, x509.SHA256WithRSA, t)
interL1Bytes := signCSRFile(caSigner, interL1CSR, t)
// create a inter L1 signer
interL1KeyBytes, err := ioutil.ReadFile(interL1Key)
if err != nil {
t.Fatal(err)
}
interL1Signer := makeCASigner(interL1Bytes, interL1KeyBytes, x509.SHA256WithRSA, t)
// sign a level 2 intermediate
interL2Bytes := signCSRFile(interL1Signer, interL2CSR, t)
// create a inter L2 signer
interL2KeyBytes, err := ioutil.ReadFile(interL2Key)
if err != nil {
t.Fatal(err)
}
interL2Signer := makeCASigner(interL2Bytes, interL2KeyBytes, x509.ECDSAWithSHA256, t)
// interL2 sign a leaf cert
leafBytes := signCSRFile(interL2Signer, leafCSR, t)
// create two platforms
// both trust the CA cert and L1 intermediate
caBytes, err := ioutil.ReadFile(testCAFile)
if err != nil {
t.Fatal(err)
}
ca, _ := helpers.ParseCertificatePEM(caBytes)
interL1, _ := helpers.ParseCertificatePEM(interL1Bytes)
platformA := ubiquity.Platform{
Name: "A",
Weight: 100,
KeyStore: make(ubiquity.CertSet),
HashUbiquity: ubiquity.SHA2Ubiquity,
KeyAlgoUbiquity: ubiquity.ECDSA521Ubiquity,
}
platformB := ubiquity.Platform{
Name: "B",
Weight: 100,
KeyStore: make(ubiquity.CertSet),
HashUbiquity: ubiquity.SHA2Ubiquity,
KeyAlgoUbiquity: ubiquity.ECDSA521Ubiquity,
}
platformA.KeyStore.Add(ca)
platformA.KeyStore.Add(interL1)
platformB.KeyStore.Add(ca)
platformB.KeyStore.Add(interL1)
ubiquity.Platforms = []ubiquity.Platform{platformA, platformB}
caBundle := string(caBytes) + string(interL1Bytes)
interBundle := string(interL2Bytes) + string(interL1Bytes)
fullChain := string(leafBytes) + string(interL2Bytes) + string(interL1Bytes)
// create bundler
b, err := NewBundlerFromPEM([]byte(caBundle), []byte(interBundle))
if err != nil {
t.Fatal(err)
}
// The input PEM bundle is 3-cert chain.
bundle, err := b.BundleFromPEMorDER([]byte(fullChain), nil, Force, "")
if err != nil {
t.Fatal("Force bundle failed:", err)
}
if len(bundle.Chain) != 3 {
t.Fatal("Force bundle failed:")
}
if len(bundle.Status.Untrusted) != 0 {
t.Fatal("Force bundle failed:")
}
// With ubiquity flavor, we should have a shorter chain, given L1 is ubiquitous trusted.
bundle, err = b.BundleFromPEMorDER([]byte(fullChain), nil, Ubiquitous, "")
if err != nil {
t.Fatal("Ubiquitous bundle failed:", err)
}
if len(bundle.Chain) != 2 {
t.Fatal("Ubiquitous bundle failed:")
}
if len(bundle.Status.Untrusted) != 0 {
t.Fatal("Ubiquitous bundle failed:")
}
// With optimal flavor, we should have a shorter chain as well.
bundle, err = b.BundleFromPEMorDER([]byte(fullChain), nil, Optimal, "")
if err != nil {
t.Fatal("Optimal bundle failed:", err)
}
if len(bundle.Chain) != 2 {
t.Fatal("Optimal bundle failed:")
}
if len(bundle.Status.Untrusted) != 0 {
t.Fatal("Optimal bundle failed:")
}
}
func TestUpdateIntermediate(t *testing.T) {
// create a CA signer and signs a new intermediate with SHA-2
caSigner := makeCASignerFromFile(testCAFile, testCAKeyFile, x509.SHA256WithRSA, t)
sha2InterBytes := signCSRFile(caSigner, interL1CSR, t)
interKeyBytes, err := ioutil.ReadFile(interL1Key)
if err != nil {
t.Fatal(err)
}
// create a intermediate signer from intermediate cert/key
sha2InterSigner := makeCASigner(sha2InterBytes, interKeyBytes, x509.SHA256WithRSA, t)
// sign a leaf cert
leafBytes := signCSRFile(sha2InterSigner, leafCSR, t)
// read CA cert bytes
caCertBytes, err := ioutil.ReadFile(testCAFile)
if err != nil {
t.Fatal(err)
}
// create a bundler with the test root CA and no intermediates
b, err := NewBundlerFromPEM(caCertBytes, nil)
if err != nil {
t.Fatal(err)
}
// create a cert bundle: leaf + inter
chainBytes := string(leafBytes) + string(sha2InterBytes)
bundle, err := b.BundleFromPEMorDER([]byte(chainBytes), nil, Ubiquitous, "")
if err != nil {
t.Fatal("Valid bundle should be accepted. error:", err)
}
if bundle.Status.IsRebundled {
t.Fatal("rebundle should never happen here", bundle.Status)
}
// Now bundle with the leaf cert
bundle2, err := b.BundleFromPEMorDER(leafBytes, nil, Ubiquitous, "")
if err != nil {
t.Fatal("Valid bundle should be accepted. error:", err)
}
if !bundle2.Status.IsRebundled {
t.Fatal("rebundle should happen here")
}
}
func TestForceBundleNoFallback(t *testing.T) {
// create a CA signer and signs a new intermediate with SHA-2
caSigner := makeCASignerFromFile(testCAFile, testCAKeyFile, x509.SHA256WithRSA, t)
sha2InterBytes := signCSRFile(caSigner, interL1CSR, t)
interKeyBytes, err := ioutil.ReadFile(interL1Key)
if err != nil {
t.Fatal(err)
}
// create a intermediate signer from intermediate cert/key
sha2InterSigner := makeCASigner(sha2InterBytes, interKeyBytes, x509.SHA256WithRSA, t)
// sign a leaf cert
leafBytes := signCSRFile(sha2InterSigner, leafCSR, t)
// read CA cert bytes
caCertBytes, err := ioutil.ReadFile(testCAFile)
if err != nil {
t.Fatal(err)
}
// create a bundler with the test root CA and the new intermediate
b, err := NewBundlerFromPEM(caCertBytes, sha2InterBytes)
if err != nil {
t.Fatal(err)
}
// Now bundle with the leaf cert with Force
bundle, err := b.BundleFromPEMorDER(leafBytes, nil, Force, "")
if err != nil {
t.Fatal("Valid bundle should be generated, error:", err)
}
// Force bundle fallback to creating a valid bundle
if len(bundle.Chain) != 1 {
t.Fatal("incorrect bundling")
}
if bundle.Status.IsRebundled {
t.Fatal("rebundle should happen here")
}
}
// Regression test: ubiquity bundle test with SHA2-homogeneous preference should not override root ubiquity.
func TestSHA2HomogeneityAgainstUbiquity(t *testing.T) {
// create a CA signer and signs a new intermediate with SHA-1
caSigner := makeCASignerFromFile(testCAFile, testCAKeyFile, x509.SHA1WithRSA, t)
interL1Bytes := signCSRFile(caSigner, interL1CSR, t)
// create a inter L1 signer
interL1KeyBytes, err := ioutil.ReadFile(interL1Key)
if err != nil {
t.Fatal(err)
}
interL1Signer := makeCASigner(interL1Bytes, interL1KeyBytes, x509.SHA256WithRSA, t)
// sign a level 2 intermediate
interL2Bytes := signCSRFile(interL1Signer, interL2CSR, t)
// create a inter L2 signer
interL2KeyBytes, err := ioutil.ReadFile(interL2Key)
if err != nil {
t.Fatal(err)
}
interL2Signer := makeCASigner(interL2Bytes, interL2KeyBytes, x509.ECDSAWithSHA256, t)
// interL2 sign a leaf cert
leafBytes := signCSRFile(interL2Signer, leafCSR, t)
// create two platforms
// platform A trusts the CA cert and L1 intermediate
// platform B trusts the CA cert
caBytes, err := ioutil.ReadFile(testCAFile)
if err != nil {
t.Fatal(err)
}
ca, _ := helpers.ParseCertificatePEM(caBytes)
interL1, _ := helpers.ParseCertificatePEM(interL1Bytes)
platformA := ubiquity.Platform{
Name: "A",
Weight: 100,
KeyStore: make(ubiquity.CertSet),
HashUbiquity: ubiquity.SHA2Ubiquity,
KeyAlgoUbiquity: ubiquity.ECDSA521Ubiquity,
}
platformB := ubiquity.Platform{
Name: "B",
Weight: 100,
KeyStore: make(ubiquity.CertSet),
HashUbiquity: ubiquity.SHA2Ubiquity,
KeyAlgoUbiquity: ubiquity.ECDSA521Ubiquity,
}
platformA.KeyStore.Add(ca)
platformA.KeyStore.Add(interL1)
platformB.KeyStore.Add(ca)
ubiquity.Platforms = []ubiquity.Platform{platformA, platformB}
caBundle := string(caBytes) + string(interL1Bytes)
interBundle := string(interL2Bytes) + string(interL1Bytes)
fullChain := string(leafBytes) + string(interL2Bytes) + string(interL1Bytes)
// create bundler
b, err := NewBundlerFromPEM([]byte(caBundle), []byte(interBundle))
if err != nil {
t.Fatal(err)
}
// The input PEM bundle is 3-cert chain.
bundle, err := b.BundleFromPEMorDER([]byte(fullChain), nil, Force, "")
if err != nil {
t.Fatal("Force bundle failed:", err)
}
if len(bundle.Chain) != 3 {
t.Fatal("Force bundle failed:")
}
if len(bundle.Status.Untrusted) != 0 {
t.Fatal("Force bundle failed:")
}
// With ubiquity flavor, we should not sacrifice trust store ubiquity and rebundle with a shorter chain
// with SHA2 homogenity.
bundle, err = b.BundleFromPEMorDER([]byte(fullChain), nil, Ubiquitous, "")
if err != nil {
t.Fatal("Ubiquitous bundle failed:", err)
}
if len(bundle.Chain) != 3 {
t.Fatal("Ubiquitous bundle failed:")
}
if len(bundle.Status.Untrusted) != 0 {
t.Fatal("Ubiquitous bundle failed:")
}
// With optimal flavor, we should have a shorter chain.
bundle, err = b.BundleFromPEMorDER([]byte(fullChain), nil, Optimal, "")
if err != nil {
t.Fatal("Optimal bundle failed:", err)
}
if len(bundle.Chain) != 2 {
t.Fatal("Optimal bundle failed:")
}
if len(bundle.Status.Untrusted) == 0 {
t.Fatal("Optimal bundle failed:")
}
}
func checkSHA2WarningAndCode(t *testing.T, bundle *Bundle, expected bool) {
found := false
for _, msg := range bundle.Status.Messages {
if strings.Contains(msg, sha2Warning) {
found = true
}
}
if found != expected {
t.Fatal("Expected ubiquity warning: ", expected, " Found ubiquity warning:", found)
}
// check status code
if bundle.Status.Code&errors.BundleNotUbiquitousBit == 0 {
t.Fatal("Bundle status code is incorrect:", bundle.Status.Code)
}
}
func checkECDSAWarningAndCode(t *testing.T, bundle *Bundle, expected bool) {
found := false
for _, msg := range bundle.Status.Messages {
if strings.Contains(msg, ecdsaWarning) {
found = true
}
}
if found != expected {
t.Fatal("Expected ubiquity warning: ", expected, " Found ubiquity warning:", found)
}
// check status code
if bundle.Status.Code&errors.BundleNotUbiquitousBit == 0 {
t.Fatal("Bundle status code is incorrect:", bundle.Status.Code)
}
}
// Regression test on SHA-2 Warning
// Riot Games once bundle a cert issued by DigiCert SHA2 High Assurance Server CA. The resulting
// bundle uses SHA-256 which is not supported in Windows XP SP2. We should present a warning
// on this.
func TestSHA2Warning(t *testing.T) {
// create a CA signer and signs a new intermediate with SHA-2
caSigner := makeCASignerFromFile(testCAFile, testCAKeyFile, x509.SHA256WithRSA, t)
sha2InterBytes := signCSRFile(caSigner, interL1CSR, t)
// read CA cert bytes
caCertBytes, err := ioutil.ReadFile(testCAFile)
if err != nil {
t.Fatal(err)
}
// create a bundler with the test root CA and no intermediates
b, err := NewBundlerFromPEM(caCertBytes, nil)
if err != nil {
t.Fatal(err)
}
optimalBundle, err := b.BundleFromPEMorDER(sha2InterBytes, nil, Optimal, "")
if err != nil {
t.Fatal("Optimal bundle failed:", err)
}
checkSHA2WarningAndCode(t, optimalBundle, true)
// Ubiquitous bundle will include a 2nd intermediate CA.
ubiquitousBundle, err := b.BundleFromPEMorDER(sha2InterBytes, nil, Ubiquitous, "")
if err != nil {
t.Fatal("Ubiquitous bundle failed")
}
checkSHA2WarningAndCode(t, ubiquitousBundle, true)
}
// Regression test on ECDSA Warning
// A test bundle that contains ECDSA384 and SHA-2. Expect ECDSA warning and SHA-2 warning.
func TestECDSAWarning(t *testing.T) {
b := newCustomizedBundlerFromFile(t, testCAFile, interL1SHA1, "")
optimalBundle, err := b.BundleFromFile(interL2SHA2, "", Optimal, "")
if err != nil {
t.Fatal("Optimal bundle failed:", err)
}
checkSHA2WarningAndCode(t, optimalBundle, true)
checkECDSAWarningAndCode(t, optimalBundle, true)
}
// === Helper function block ===
// readCert read a PEM file and returns a cert.
func readCert(filename string) *x509.Certificate {
bytes, _ := ioutil.ReadFile(filename)
cert, _ := helpers.ParseCertificatePEM(bytes)
return cert
}
// newBundler is a helper function that returns a new Bundler. If it fails to do so,
// it fails the test suite immediately.
func newBundler(t *testing.T) (b *Bundler) {
b, err := NewBundler(testCaBundle, testIntCaBundle)
if err != nil {
t.Fatal(err)
}
return
}
// newBundler creates bundler from byte slices of CA certs and intermediate certs in PEM format
func newBundlerFromPEM(t *testing.T, caBundlePEM, intBundlePEM []byte) (b *Bundler) {
b, err := NewBundlerFromPEM(caBundlePEM, intBundlePEM)
if err != nil {
t.Fatal(err)
}
return
}
// newCustomizedBundleCreator is a helper function that returns a new Bundler
// takes specified CA bundle, intermediate bundle, and any additional intermdiate certs to generate a bundler.
func newCustomizedBundlerFromFile(t *testing.T, caBundle, intBundle, adhocInters string) (b *Bundler) {
b, err := NewBundler(caBundle, intBundle)
if err != nil {
t.Fatal(err)
}
if adhocInters != "" {
moreIntersPEM, err := ioutil.ReadFile(adhocInters)
if err != nil {
t.Fatalf("Read additional intermediates failed. %v",
err)
}
intermediates, err := helpers.ParseCertificatesPEM(moreIntersPEM)
if err != nil {
t.Fatalf("Parsing additional intermediates failed. %s", err.Error())
}
for _, c := range intermediates {
b.IntermediatePool.AddCert(c)
}
}
return
}
// newBundlerWithoutInters is a helper function that returns a bundler with an empty
// intermediate cert pool. Such bundlers can help testing error handling in cert
// bundling.
func newBundlerWithoutInters(t *testing.T) (b *Bundler) {
b = newBundler(t)
// Re-assign an empty intermediate cert pool
b.IntermediatePool = x509.NewCertPool()
return
}
// newBundlerWithoutRoots is a helper function that returns a bundler with an empty
// root cert pool. Such bundlers can help testing error handling in cert
// bundling.
func newBundlerWithoutRoots(t *testing.T) (b *Bundler) {
b = newBundler(t)
// Re-assign an empty root cert pool
b.RootPool = x509.NewCertPool()
return
}
func newBundlerWithoutRootsAndInters(t *testing.T) *Bundler {
b, err := NewBundler("", "")
if err != nil {
t.Fatal(err)
}
return b
}
// A helper function that returns a errorCallback function which expects certain error content in
// an error message.
func ExpectErrorMessage(expectedErrorContent string) func(*testing.T, error) {
return func(t *testing.T, err error) {
if err == nil {
t.Fatalf("Expected error has %s. Got nothing.", expectedErrorContent)
} else if !strings.Contains(err.Error(), expectedErrorContent) {
t.Fatalf("Expected error has %s. Got %s", expectedErrorContent, err.Error())
}
}
}
// A helper function that returns a errorCallback function which inspect error message for
// all expected messages.
func ExpectErrorMessages(expectedContents []string) func(*testing.T, error) {
return func(t *testing.T, err error) {
if err == nil {
t.Fatalf("Expected error has %s. Got nothing.", expectedContents)
} else {
for _, expected := range expectedContents {
if !strings.Contains(err.Error(), expected) {
t.Fatalf("Expected error has %s. Got %s", expected, err.Error())
}
}
}
}
}
// A helper function that returns a bundle chain length checking function
func ExpectBundleLength(expectedLen int) func(*testing.T, *Bundle) {
return func(t *testing.T, bundle *Bundle) {
if bundle == nil {
t.Fatalf("Cert bundle should have a chain of length %d. Got nil.",
expectedLen)
} else if len(bundle.Chain) != expectedLen {
t.Fatalf("Cert bundle should have a chain of length %d. Got chain length %d.",
expectedLen, len(bundle.Chain))
}
}
}
func TestBundlerWithEmptyRootInfo(t *testing.T) {
b := newBundlerWithoutRootsAndInters(t)
// "force" bundle should be ok
bundle, err := b.BundleFromPEMorDER(GoDaddyIntermediateCert, nil, Force, "")
if err != nil {
t.Fatal(err)
}
checkBundleFunc := ExpectBundleLength(1)
checkBundleFunc(t, bundle)
// force non-verifying bundle should fail.
_, err = b.BundleFromFile(badBundle, "", Force, "")
if err == nil {
t.Fatal("expected error. but no error occurred")
}
checkErrorFunc := ExpectErrorMessage("\"code\":1200")
checkErrorFunc(t, err)
// "optimal" and "ubiquitous" bundle should be ok
bundle, err = b.BundleFromPEMorDER(GoDaddyIntermediateCert, nil, Ubiquitous, "")
if err != nil {
t.Fatal(err)
}
checkBundleFunc = ExpectBundleLength(1)
checkBundleFunc(t, bundle)
bundle, err = b.BundleFromPEMorDER(GoDaddyIntermediateCert, nil, Optimal, "")
if err != nil {
t.Fatal(err)
}
checkBundleFunc = ExpectBundleLength(1)
checkBundleFunc(t, bundle)
// bundle remote should be ok
bundle, err = b.BundleFromRemote("www.google.com", "", Ubiquitous)
if err != nil {
t.Fatal(err)
}
checkBundleFunc = ExpectBundleLength(2)
checkBundleFunc(t, bundle)
}
func TestBundlerClientAuth(t *testing.T) {
b, err := NewBundler("testdata/client-auth/root.pem", "testdata/client-auth/int.pem")
if err != nil {
t.Fatal(err)
}
for _, leafFile := range []string{"testdata/client-auth/leaf-server.pem", "testdata/client-auth/leaf-client.pem"} {
if _, err := b.BundleFromFile(leafFile, "", Optimal, ""); err != nil {
t.Fatal(err)
}
}
}

16
vendor/github.com/cloudflare/cfssl/bundler/doc.go generated vendored Normal file
View File

@@ -0,0 +1,16 @@
// Package bundler provides an API for creating certificate bundles,
// which contain a trust chain of certificates. Generally, the bundles
// will also include the private key (but this is not strictly
// required). In this package, a bundle refers to a certificate with
// full trust chain -- all certificates in the chain in one file or
// buffer.
//
// The first step in creating a certificate bundle is to create a
// Bundler. A Bundler must be created from a pre-existing certificate
// authority bundle and an intermediate certificate bundle. Once the
// Bundler is initialised, bundles may be created using a variety of
// methods: from PEM- or DER-encoded files, directly from the relevant
// Go structures, or by starting with the certificate from a remote
// system. These functions return a Bundle value, which may be
// serialised to JSON.
package bundler

View File

@@ -0,0 +1,56 @@
[
{
"name":"Chrome Browser M39",
"weight": 0,
"hash_algo": "SHA2",
"key_algo": "ECDSA256",
"hash_algo_expiry": {
"target": "SHA1",
"effective_date": "2014-09-26T00:00:00Z",
"expiry_deadline": "2017-01-01T00:00:00Z"
}
},
{
"name":"Chrome Browser M40",
"weight": 0,
"hash_algo": "SHA2",
"key_algo": "ECDSA256",
"hash_algo_expiry": {
"target": "SHA1",
"effective_date": "2014-09-26T00:00:00Z",
"expiry_deadline": "2016-06-01T00:00:00Z"
}
},
{
"name":"Chrome Browser M41 and later",
"weight": 0,
"hash_algo": "SHA2",
"key_algo": "ECDSA256",
"hash_algo_expiry": {
"target": "SHA1",
"effective_date": "2014-09-26T00:00:00Z",
"expiry_deadline": "2016-01-01T00:00:00Z"
}
},
{
"name":"Mozilla",
"weight": 99,
"hash_algo": "SHA2",
"key_algo": "ECDSA256",
"keystore": "nss.pem"
},
{
"name":"OSX",
"weight": 99,
"hash_algo": "SHA2",
"key_algo": "ECDSA256",
"keystore": "osx.pem"
},
{
"name":"Android 2.2 Froyo",
"weight": 1,
"hash_algo": "SHA2",
"key_algo": "RSA",
"keystore": "froyo.pem"
}
]

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,11 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBkTCCATcCAQAwgYsxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRwwGgYDVQQDExNjbG91ZGZsYXJl
LWxlYWYuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjEb98b3L+COUBe8H
vtt4REtsGig33wUYUDFVQDkiCXKW+CZ83FSYjyYzZTD23M4ub285ECtpJIzj/qJK
kImt4KBJMEcGCSqGSIb3DQEJDjE6MDgwNgYDVR0RBC8wLYITY2xvdWRmbGFyZS1s
ZWFmLmNvbYIWd3d3Y2xvdWRmbGFyZS1sZWFmLmNvbTAKBggqhkjOPQQDAgNIADBF
AiEA+hlls8mNtLv47Rr8B7dGGKCDa1/qLHectmhdAnyrTVwCIFnAgTgiPAerNAct
KjOJZdHDuaBGeu5o+5SLD232m/2E
-----END CERTIFICATE REQUEST-----

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-----
MIIBzjCCAVQCAQAwgYsxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRwwGgYDVQQDExNjbG91ZGZsYXJl
LWxlYWYuY29tMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE1iK8MOHciKSRv7f86hyK
3LglBebEs/aeNlOFg21HjLsHGLGnTkouiryC1rPJxwOSW567g5YWemaSPKo9D7Qw
h/8EkqgDhDiI2II39l8Xr3QtH+lk+sxFm5ZIZVvbz3QvoEkwRwYJKoZIhvcNAQkO
MTowODA2BgNVHREELzAtghNjbG91ZGZsYXJlLWxlYWYuY29tghZ3d3djbG91ZGZs
YXJlLWxlYWYuY29tMAoGCCqGSM49BAMDA2gAMGUCMF4FEJtaKJXcrj6ZHxtFGWp2
IIBmMKRctjcQLm46S6toh9oT/TQGvIYBTiyYmxWhVgIxANsA3GzCIPSiwhKiBFxv
026lKuw4Ci9mlH4pJ7cJnCgSmxHP6jr8O+XovT7SzN1zag==
-----END CERTIFICATE REQUEST-----

View File

@@ -0,0 +1,6 @@
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDAEwBewBsRvgqvyy/aJ0NsoTqkbwFeu3bL6rLxLGcxCfKzlOYz5te8j
BR4cPZbv5WOgBwYFK4EEACKhZANiAATWIrww4dyIpJG/t/zqHIrcuCUF5sSz9p42
U4WDbUeMuwcYsadOSi6KvILWs8nHA5JbnruDlhZ6ZpI8qj0PtDCH/wSSqAOEOIjY
gjf2XxevdC0f6WT6zEWblkhlW9vPdC8=
-----END EC PRIVATE KEY-----

View File

@@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICGDCCAXoCAQAwgYsxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRwwGgYDVQQDExNjbG91ZGZsYXJl
LWxlYWYuY29tMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBKd1KqqCaXulFe8vW
Ed2vEoDKaEaLUijangPSSovty8hOqecN1rBDb8nIdQ0HOE6u57x+II0T6ju+dtXl
7G5qwGMBCtxdUXXsRSSedw2irlJ2DqoiOaXByo0w6pK9ggAYd3BNdR4Nrzx3+N76
TKfNqIyhczbUiQUp51e2m/foPs4r3qigSTBHBgkqhkiG9w0BCQ4xOjA4MDYGA1Ud
EQQvMC2CE2Nsb3VkZmxhcmUtbGVhZi5jb22CFnd3d2Nsb3VkZmxhcmUtbGVhZi5j
b20wCgYIKoZIzj0EAwQDgYsAMIGHAkFlyII6rIxYiv7S5RwwMi8G0qACjrbb1SMa
oZA9vG+3G/SRcr5WmzKYgG09OjLT61KYfXu4mybdXXlXzHbx07llRgJCAKRuWU3O
3elclbkZvAGduasj3sj0Uee3nLG0YmDvz95sZPIp5JH54naeF4KKF6NJQF/rl9TW
BHa3MZqM3JM7vMkI
-----END CERTIFICATE REQUEST-----

View File

@@ -0,0 +1,7 @@
-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIBnn+dzn3tVUMj9s3nRs8I7waob9iLi/QhsIj5leFRj44hbWGwfymm
OHLJR1jIG8VzyYaNssSPo7ioMpgOpX+R14+gBwYFK4EEACOhgYkDgYYABAEp3Uqq
oJpe6UV7y9YR3a8SgMpoRotSKNqeA9JKi+3LyE6p5w3WsENvych1DQc4Tq7nvH4g
jRPqO7521eXsbmrAYwEK3F1RdexFJJ53DaKuUnYOqiI5pcHKjTDqkr2CABh3cE11
Hg2vPHf43vpMp82ojKFzNtSJBSnnV7ab9+g+ziveqA==
-----END EC PRIVATE KEY-----

View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDGDCCAgICAQAwgYsxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRwwGgYDVQQDExNjbG91ZGZsYXJl
LWxlYWYuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0C6SSsXf
use2IV8+6hSYqSPQdoQwZ5BYQnSxuKylArCrMXx8JGHrJP6Pj7GxRmH40v9u9VwZ
vcrQOm8yUTuzAEf2Kd3uvXmVKJb2vc0BopsflpSEOLEuddTSHlHgdVHylqpbzB7Z
rmyXXuWTtTFEaGmPVUmWcOBOy6pc/7hZv7HkTjaHLQu/uohic/NjO0oJaaUwds6m
uwTCNSmMvtvoP51pyQJeuZjYIoWnnu+/DbtZYmH44VbHD0U+uSNKLZa4beWqDq5Z
DwQvEVkuLqL331awzgIf0a4bhP+uc1kdWXZ8V+8aBbqtq6g6o9HdrzgNRR+9S3Ev
EelCrxuWw9FQ3QIDAQABoEkwRwYJKoZIhvcNAQkOMTowODA2BgNVHREELzAtghNj
bG91ZGZsYXJlLWxlYWYuY29tghZ3d3djbG91ZGZsYXJlLWxlYWYuY29tMAsGCSqG
SIb3DQEBCwOCAQEAguCRmg2XzRlcq6neK/IdHZb+EeXSPo1BXsXrhzZZTpDTw4pC
Kp+L9tG97t46rnlhRpwqY8zL/sXxBAlRB3G+VpsgLQzt18Gq0ZGBTjAHZBOeraKS
/GMzig241SNvvvqEQR540TAZnzRgJzGJxCGQkhaXKIrGoh6yqiiTUkn5iu+K737U
wX5xa09OdUnOc6MBbHFaynyWHZYjXzKv7zuZE+0VKjyKnLuHtRw8AS7zX/TkRf39
mgIp/hg3ZjWKTKDzudfMRVYS6nsbufViDTsOd7jMJa393H/wtKN2F+GyN8EIvuNt
eVECUulWhbugcCAv3qgpiTgyx0eDSLBu9Ct/Kg==
-----END CERTIFICATE REQUEST-----

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,24 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIEGDCCAoICAQAwgYsxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRwwGgYDVQQDExNjbG91ZGZsYXJl
LWxlYWYuY29tMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA9xYBDoV2
tPx8lqZ/bH/wLvoPsg1/CXeknvRcNuxw1gu6c3IJBrKZlkFtiU6Y8FADiUBOVab/
Y0cQ/9EdeB2srPH4M5KNiPdWZPgxARWnRq5Ez8pvVASP2E2Zya1UnH5iJBau8e6S
wBl8UaXnGwcA+CUv+FXcZtdoFh0Lqt3AdItQOkHVjSE6Cfiv5lsSW0ikMcoHFOHN
ps4/9A4A/griT5lRDqQIycN7WD2k4+aKVreCWxbSteU35yIDJV6PGUtw8k41arJ+
kwuwYM3+YklR0Dsj0RxXn07oLqnf6IeNUogGhNVO7RvLdpfvrhlevHVXmmYj40fk
GjU15KkZOKigMw/gDInI6Sc2jp8oPX9tjkaQYkF2t7AWOq01lh5TleMIoBFUqVcy
+X/qejla0JaKCEyt/fiPUo7/SgucyFl8GrKfSdELUOKx5Vr2ZZ48QSfIlXle+tGt
FD0AYUsO0ud0wclW5C+g8E27raTuR4RaZOj8/pmB7XNDszwxQ/97dBRpAgMBAAGg
STBHBgkqhkiG9w0BCQ4xOjA4MDYGA1UdEQQvMC2CE2Nsb3VkZmxhcmUtbGVhZi5j
b22CFnd3d2Nsb3VkZmxhcmUtbGVhZi5jb20wCwYJKoZIhvcNAQEMA4IBgQAKrSiJ
qfeYzFQgCx+lj2rTDdGbiB9JoIamyTULWoN4WCxwS8KJWFQXOf4SkibHNLMMqBFY
RpU/5mvjXVrKboNgzp6+QoWpdN/AHu6ldFz+o3Imna1yEscGZA7Qfie5hrf9kePe
PCPEqnsG8j9qyip3W3p9/SsM2xUaei+YGVmAyzpXlYq0WZGsz+wVJ2zc6ZcxzTsC
HN8cYafVR0ZmhruRUjhRM9mI+XXFYjk11lNo907Hue5n1acvqofz4RID2rx4e2nq
2DH4HZ1UvPDx93FJmMu/c8vLyMz17wPXCaC2M1SeVdXQeGg7JETvL95hJ++o3vLL
/QJehGooK7Rcht4lc1logn6tQYNyRpIKiN6Bb+lBujTzVT461yPTk8D9xY0+jHIO
nKXIXKVkoXXiL70aR0ZCviHx4sNOSZyqwhwiUedNP0rAacbk7AY4cdJWSHvcVH3/
qKlTkwOyr5AGX/SK/JTDvVjQWW95OI4a1xqEMlCN5jMOrQFwa181JMx4cmM=
-----END CERTIFICATE REQUEST-----

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,30 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIFGDCCAwICAQAwgYsxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpDbG91ZEZsYXJl
MRwwGgYDVQQLExNTeXN0ZW1zIEVuZ2luZWVyaW5nMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRwwGgYDVQQDExNjbG91ZGZsYXJl
LWxlYWYuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtY3sRFA2
cmwm4bEttx1TVRENJnd1re3fiR8YccUPcnmZ3uNY1sfnaEiHfTsxk10hbLOo24de
YAZSC6w4W1ErGZnPO21kTrdlKUysmqfwLcjLGvTj7/3HKnbpfFQx3sV91+InI5HP
141mn78/Zgw22SZizysbn1x0QpnjK9WFZLdY6o7hNkAp53Jx9g85PiRROcLh+EH6
WMkxUUKx9zie0MPydFkiBlR+nGZ9SO5DGPKJGPVk7YF5n2XQNBWUXUq+cLqisAkS
Op7FB1AryMiQCLVp6FATt/CRXA0O3d0hd9HirnLU6QMf6SgguYzFw/VjWF7AoCdX
gNhtAo8hB5wR3/srInRhDz2YKhcTC8F6gUErCXKY6QF8QV8I8H10/Drp2MJwlxW1
9AfmSFogIs/Y5KPn0kMmcUhtMtMtx1xa21OdmbgD0vbMFE6cqoKdSYfImhK5tKfa
xgQu/jPlshBztYp6jXtlfcYVQ4rcnHM/hqm6HJO4hh55U6wrw3OGv/HSfwjs63oS
JJgqLzs8WWVJKahWozCotyAGrIF+/mCcsciMm7NsWWWsPizB275nDWh1t4zhUq3W
P4A46klZqF2UuNCkxJsh4Dgz8C2xMReRmPkDN/hTE9iOPAunk8xL1dqtooLxGSKf
oO4YLlBgEqYottodEFG3LUEycps65m4eIAkCAwEAAaBJMEcGCSqGSIb3DQEJDjE6
MDgwNgYDVR0RBC8wLYITY2xvdWRmbGFyZS1sZWFmLmNvbYIWd3d3Y2xvdWRmbGFy
ZS1sZWFmLmNvbTALBgkqhkiG9w0BAQ0DggIBAIry/y2+Q9mLxlNZz7mKemrqj5Iz
b+0IyaM6uReys6O1YcRf2KfnZ4TtURRa1ehjqOJsyYpLFEtzvATS9SktrcL/YnvL
kWctJWEGJ0PJvhMpAy0uZy9uwI7moltcDtr1HdOG2riqfbhxTY+/g8mFhqWl5vFj
S+ok7sSnztN0NQmDpXfuAVZIQQwEioeSDrcT2EcCf4ltuB23wzTMYqhflHZucEB6
eDr+7n8zcv4pXHvING6yR5G8eklR79zjlxO9QadNBjCWVllz9c37FMk6CvANfGLs
YbIJTXYVPdmbyKMuYzIzgL1RiqTX4WNUVI1AputdGXytGkNPl7KbVvHsyp/A6tLR
fZu6WW2NwjDC2s6HBYseo8huEIwG8zXV2Et1+yGZB6YHsw5Jv6W4UHD7pgpMiacg
G4FOex7h66tTPGGHKmuouumCoGf2Zyr7oVeixx8OHwrl/tzoiyRzcmqZ+wW7IJA1
Nx8exenau8Kq+lCkj8dJObYNZNEQ7Hljo5w2ATChTXYaJYc49KOoSKd3d/YyVqIQ
Qoeq+MTmxQuDcWbNZCgDnQZEPqW4Imv1cFApPH0t5JCRSutd7WNnNtC+kSPcXYhc
NV71ovaHrio/7bXeNYKxJoYrfTe2mxMwrsnfuCwK2TgrfAL9yVQ+pfn2StopJOaN
iznpt2q5PEkYOJEj
-----END CERTIFICATE REQUEST-----

View File

@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJJwIBAAKCAgEAtY3sRFA2cmwm4bEttx1TVRENJnd1re3fiR8YccUPcnmZ3uNY
1sfnaEiHfTsxk10hbLOo24deYAZSC6w4W1ErGZnPO21kTrdlKUysmqfwLcjLGvTj
7/3HKnbpfFQx3sV91+InI5HP141mn78/Zgw22SZizysbn1x0QpnjK9WFZLdY6o7h
NkAp53Jx9g85PiRROcLh+EH6WMkxUUKx9zie0MPydFkiBlR+nGZ9SO5DGPKJGPVk
7YF5n2XQNBWUXUq+cLqisAkSOp7FB1AryMiQCLVp6FATt/CRXA0O3d0hd9HirnLU
6QMf6SgguYzFw/VjWF7AoCdXgNhtAo8hB5wR3/srInRhDz2YKhcTC8F6gUErCXKY
6QF8QV8I8H10/Drp2MJwlxW19AfmSFogIs/Y5KPn0kMmcUhtMtMtx1xa21OdmbgD
0vbMFE6cqoKdSYfImhK5tKfaxgQu/jPlshBztYp6jXtlfcYVQ4rcnHM/hqm6HJO4
hh55U6wrw3OGv/HSfwjs63oSJJgqLzs8WWVJKahWozCotyAGrIF+/mCcsciMm7Ns
WWWsPizB275nDWh1t4zhUq3WP4A46klZqF2UuNCkxJsh4Dgz8C2xMReRmPkDN/hT
E9iOPAunk8xL1dqtooLxGSKfoO4YLlBgEqYottodEFG3LUEycps65m4eIAkCAwEA
AQKCAgAAn5EdFu1o7SghBDu08jvUAe/6ntRfmX53+Qxbb6LC8NnvYvZuHleUCxO0
AV0FNX+k4OUGg+t2Bu+HLLswzRGJz/ZfLNv4TTbismmxSjxP2+2elRKnQ0bIxYm9
rIhTTHhHInah76E4Czs79ysfjZEuo6wZK/u3S1j21ZJrFxuTIfIDNCRfzE6YhdMQ
VjMLHJLO0PV3pbpXTbGGpuT3hVE+RD3z2k58mROqM8vgUTkXv9VqqYUEL7qcKnxR
gXV18IjA2FMwqYdPfjYM9WCBGvcroHvRmVzH9+J332+aoWS5BZZypOBIQIN+iG28
VVhkeNYzenfM1PW+8n9FT/p8DTQegEJItmWOUsf0eNE6beHEOJZAuqnmLoKpD/je
4DXmcWaBxKTyCjLdG6BHPjAoF/XkNIW5gMXO1b3iGDYBWl9ChWfpzwuqVWDJerAy
9UH3VLPEHpZOahs6GKxMkYr3dAQRUDGW/Cj+a28VaAoeI99mszP5t95rUjq9YDx4
FI29BOwQOjTiF8qtjsJujrwjnbYbWV8xDcho4oNPGskzOTIxL5lOzzhWCMdo2qg5
skG6qOmJo1Ccezd2m54njHi46vj0tFePPnYcFtUZjnCxWmh1N4olc5pcmQA+y1j0
516JACIyd1VQ5OzFL/DwYYR5aUekZoDFqr3AEKadMgvC/WTXAQKCAQEA2NWpbe6K
tYNvVk4XNyG9IzT/KfJ6uVAbwG5gebVbmNF87H20l9gbzPlZs0hmFw8sYPDVUVx3
OuWD0/ertCrKANX/9cfWa5fk8gmC9ESxOR7qMdiKF/9WUGfgQEfcyXms3L6N/xIv
Ds/WOZMBnODHT7Y+th95J3sQqHy/CwYw5k4JIDXjaEVC8LawdkUjIzjnIJN+Rbou
CD/5HFOmlg9vRaIXi5UY8lG5g0J0wLNXISF6SS+ROEOD6PKS9+M49vhTcbpOzKOL
DkMqnRM8qFw58afX7/g3jNLUZLRRVd4xXsIhTnLe4+117m76cyaqPV7Hq0Ss5ZyD
f+oBiv7KTusxqQKCAQEA1ljrKo0BTbzVLlZPb/SGRmHE+W23xYH3Y4P635gzEIRB
fqptIvRAWTTC4rC7c2aLat3tlmm2TG2zcOAiDeP1QltEeOCIcOXXNkGjDd0LmaVW
utkzMdRhBmK3u0GteZWI8z/G4ZyaddhhzLV9RB5hXcw0g46YLGY/oN+U4VAeypHx
nqI181dK+D7L8tJnWkWqqUfHpiNAXPXI56rDbetCv8tU5/WuryaZEPHqbLhbqhdP
ovvxcRXG4FzoPc/gY1db7fm46WXQHBJ2cZ0NWc5Clk27VxLzxsptzesCYE+juSKS
zldIww9WkAPbvGS5eKgqLqYh/CCSiwjmpDHUQ803YQKCAQBUoVgGsyLqY4lSCxqe
hwmWMzogOibSK0UZnzsCZdmBVMpIV2vkFBINt4jeI7TM7Twp/fWUUt2qXChO1Azt
PgInv16upDe5OMi/+xxkkGcHX1yS5exIH32l1lU9YY74CAiDA9DSLFu3kUEQqaLo
gwbnwr7JQJF96ld/G2lJOTpeuThwnPfMG7Rb1UIcdzGWrr/vBAI13svWpnlpJ/EO
AqowaGp+LUxWT7VzWL3O9HBeWv2qkOlCJ3/VrM/V9pamNhgDfG8DChXdFDQOqJJ1
N8HZ4uOyIpQz35nMUGCqfhWQ4X40azs5hNYRoLkZto6dc1/FJgHBgIwGoePGR1nY
4Y05AoIBAGjH/OXHGj0LM1c8gAaljUI4pxabiPt3Bh1Stj+5YjUPwgiOfV1Z817o
SOCSLoBCP6MVLACcWq5P7ikel+ccaZdvkDBa6rft01/FhFTRmssYJSaf6MPSI0AA
3/odKBVIgTMQGUPOzz8OcPimO78h7szwdzdcbI0/ypj00w21oee6ole+ygrTwGVM
JVzld/qMFdo8qZ9QmjUXPYfqVTCVkgK5/h6KXkNm5ep/p+5PzRd/38E30hZK4/Zn
1GvrA7DsUpcpvTfzOmGOsPHnKVCeYLSi+RKexCaIbFb+LCIyrEFjYkqWJo6cH9+0
0yTsRDJ0WnKFahWbQqfOyRi49x/R4OECggEAGPyBEn6253gPawT87wIEvTlaoowp
Of3aqpCVuksb4GinD/jGPmGwUOqrHGD3YEuIPSMQ2oBeEaSMPgMutUwTMt36J8rG
L2zG5N4jvj5gv1zStdV03OEAae2QgTwNX6vI5NbQLT4R9yd0CNVsbRJrC0PJjEoi
d+SOCU8jiYx7/h68n1xbgtkS7sFzXc4DMc+uUKoprTloZj7pKLX/NqpUKZKiWwAe
Ss4mRqFIywC7uyazyr5PwOyuPE02PgUadeAJOsHTVspADf+gMG8R1OppjPs6Lqrj
m2k2JUCh7tjMGyePf/HqILgyByWSXXuTlvUXD6i9qDThg+QbyZhGcPOtAA==
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,7 @@
#!/bin/sh
rm *.pem *.csr
cfssl genkey -initca root-csr.json | cfssljson -bare root
cfssl gencert -ca root.pem -ca-key root-key.pem -config root-config.json int-csr.json | cfssljson -bare int
cfssl gencert -ca int.pem -ca-key int-key.pem -config int-config.json -profile server leaf-server-csr.json | cfssljson -bare leaf-server
cfssl gencert -ca int.pem -ca-key int-key.pem -config int-config.json -profile client leaf-client-csr.json | cfssljson -bare leaf-client
rm *.csr *-key.pem

View File

@@ -0,0 +1,18 @@
{
"signing": {
"profiles": {
"server": {
"usages": ["server auth"],
"expiry": "43830h"
},
"client": {
"usages": ["client auth"],
"expiry": "43830h"
}
},
"default": {
"usages": ["server auth"],
"expiry": "43830h"
}
}
}

View File

@@ -0,0 +1,13 @@
{
"request":{
"names":[
{
"C":"US",
"ST":"California",
"L":"San Francisco",
"O":"example.com"
}
],
"CN":"Intermediate CA"
}
}

View File

@@ -0,0 +1,14 @@
{
"label":"client",
"request":{
"names":[
{
"C":"US",
"ST":"California",
"L":"San Francisco",
"O":"Client Cert"
}
],
"CN":"Client Cert"
}
}

View File

@@ -0,0 +1,14 @@
{
"label":"server",
"request":{
"names":[
{
"C":"US",
"ST":"California",
"L":"San Francisco",
"O":"Server Cert"
}
],
"CN":"Server Cert"
}
}

View File

@@ -0,0 +1,11 @@
{
"signing": {
"default": {
"ca_constraint": {
"is_ca": true
},
"usages": ["cert sign"],
"expiry": "43830h"
}
}
}

View File

@@ -0,0 +1,14 @@
{
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"C": "US",
"L": "San Francisco",
"O": "Root CA",
"ST": "California"
}
]
}

View File

@@ -0,0 +1,20 @@
-----BEGIN DSA PRIVATE KEY-----
MIIDPQIBAAKCAQEA27xa+d5kAGDnxWkmZON9rNHw73/M4cwKpKGMpxGEdMt+u7wB
Nt6tCH0v6dHo6726L6YUopxSzKahtzngxmT8G/P2dcbiVUm6r2N1T7zX5+9tnwWY
PcpexdX/mXUnoB1yNHSckDiG0k5EGlQTTFXmg22aChvINIFaoEdR5IW3fOdiIX0z
NWUBQ6eezsFuoy1anIb9WjOcCtmdvjPFtWdmZwGVfUp/CmJ+720GijTmsRB3dCqp
QoxsFC+BtbtOtgX7pKPPsmICaYTgDqaY6Oc2HyWvS6xnl5uaHa33sFz9EisIy48n
UbajWnLN8+bqSb+iIbR9xKxe1NRUO5rvJtXCmQIVAK2dU+z5hzWPAnuHp19T9y8J
Km8JAoIBABk907ebpqMBTGcJ6kQiJshgmao2zN3uUWiA3GCrdnq8JxumqoRTbsLQ
sxh+nvw24U8bK94NhhoUmQHfhl1GWb4seSUygoN7NUOC9wDH9QfrEi9S9eUS07gs
LQ4QEYJPbxC1Wu8MIXJ2RpuaSFh+TClsasaGK54JOwNp4Nvh3CXYfwYL1Jtt9vOc
tN2tF8Rr9zQrSgZDdsJvr/cIprxhY8JB4D54Bq77D4zzULz792TKTHXyjhObL4XQ
cXz8tWloYF/wC8ME64CpVOx6GveN/cy6rINLG4T9epmheVDVmM33Mg2KgY+L+V3l
l3QxBX/uygjuzCmK489u+OrP4cnXxJYCggEAVl000S2oxe2zAnt+oaeHc8QUO5B4
pb4k9MoLgM5AXGQQMmZcMwUaiSDe7q7FsM47ARXBI8jZkR/ZEAZuhoK/7qgo9VQV
tW95SpMjesaj7LK0ocHU2djvUMzxZDWU+zkd2aJTusnbwWKwTXK64WAv97aKbf+O
Avnjln3MkqfMzqR24w0ccdr8pZ9yTRyRyC6tf9G0/vnvSbZEALSsLXjuB6FIrpma
30S5KL4IR6cBIKlUHC9rf6ET3lLDFlM3B7YCVw/8VpENATd+sEez8f96lgQNcWSH
8Us611d7wGOjB6pDe7FueX+CeLFUzBEJ2YdiMRnQMVZ9nFY8i+s/KH2FFgIUeuC2
1y9hgnFoPYic5nnISNkQKP4=
-----END DSA PRIVATE KEY-----

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
-----END CERTIFICATE REQUEST-----

View File

@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEA5Qp1fr49LFT8vEqhywJSPscJz4VoM49lXrw+jc9WbtQXkOZu
cuAlgn/hpkRkz+/mvwuddBcOq6ZiJwdDYMccENqwtPHybQp4o5Rh/1YxDyB2OZUW
gKJnDPEYMeiTrr/QaWSSqa/aclDFedyXlkW1rix+ZxDHcpPTaXrUtoFsbePw9tEJ
YSv7R5ETq3C5hFHXcCBncBPCcyPPHO+xbruqYDLRyr5eBhh9Mr2+E+z7wnR/FdrQ
h+27fK6F3fTEshVcu7jkZH6LoqexLFURFJpXGORL6UdeNSl22aojkIrrCfru+phF
EDZPMAkdfwlv4ovb9QSjlY18AnxLSF44NADycdVTUkh+l6A6VBEy/nkOCdBS/VzW
7A7mGq+/sw9Ddf+b0qNxyzUUEfX9JW7MrLFgRDfgsStfccj7pq9tWdG1NH9QeR+d
lUhdKtyNSbvcEaPfeGu2IqXRnJmdpQoZk+695s5QU2Jd6aFNuqt+L/MBvVOyb+H7
GSfkRnmwyPk0d0BumKHdOpfaBGGcVlwWO0kzrPMLk7t4bkp2p/yKVp1dNe1bHOtQ
+RvkK6nMWXV3JLd2p2Sghe+SaVJye77qME5ph5W/CiDGvZjr3Lhl+D+ps/IE4tmq
5f2i9u5I/UNCYL/8tXm3tgcdj/UxevlTQwy9yf70lZRXqi7dq3gCASp8rS8CAwEA
AQKCAgBPyl/6Qm3vNsBBHELXBTz/r7lEOTZ+19K5uRyVrIhw3aRED3KkxF9s4f4L
PUJdija5kWNN4QZ0V+dTr10SpuqpGHZ84tjQkdhLLFMjb7Rxj56AGucW8vyxboA+
SsbAFwSU4ruRL7kLIAZbmLSaXjiXr9ptL1Q8HzGESo0180qB0enNIi+BUaAdY3YV
wJRwe05xOmiui8Ou9uedLgeDCw+kqa+aUM1SlE9xUNaZ/HIMYScwxuTkpbYuDmKG
W1H2tCh1IUk3lTox9Pds+UmVAtuayVWEtB8mqAZGd9Yh8bNF68w7MrbEmhbmJhbH
fdMjehOrfO08GWj9OK3FTUWJIFdVFdjE6M5F/9kKjZhTH6Q6N0GU2M65v+R5CRWW
3vJu7Nhfek4gRa9zWVjHNQbpBoDDnB0MC5s7MQ6HnVQ3u4dZMDYo/XQrc/uIeaT+
cuN8IW1u9nEGdNT0+xmV40oRgXGMcWH9kkzctSHyt7d+BiWZLL2PFGV/RprNUklp
1kFl2p7piof4mgueiU/iJvosl9E1eVdWQgrrz33NMuO+Ox+PHgIyWHC6A9FDdscd
9FQ3AFmGIVNgRzJi3V0/v5ESqkdJ/QpmHwS0uoP7zdK4/zeCz6Nav/QdmkEOV6Si
GLn7k0xxdRUNW5dHjLXacmy279fB3rM8PxkAUbLh4VKHVlGNyQKCAQEA54YN0wRv
AI/lY/Z6wpolMpA7Gt2305yfFP+9oTORWxR8MKtO/aCmuxqF9qX/PMRa1HeXxCCM
O9zD3BqChNS3kwKSVlt79KiY4Q6HflTTeG55kXsmRxxljoi46zgcuwJPPlexciO4
nMoaac3/pQXnuwu65iLyg0/8Nw609C9xXyjQM1szeow9dL46/i2g5kh43Qu560Um
Mjcv9TOV4G+efatOv0sQGUgHHoKQVw2kVvImYRkiXHg/aaRqGSk3zlvoVTQVN+hJ
rn0Sh+AlBY7NiG0r/wT7p+PGASq/vG3JX+cJ0V6KXaI0s9f7NLNkWMx6cITO77Je
pbKmIYaoadeASwKCAQEA/UE19ErMQyX1o2pScaWZ4JpKY2R4i13VvPbhL0ztDJdv
iPhO/HaaWTyn29Tve7KpRlbr5PMWy3Szk4NnL6Tn/PCywHRPniW/UzgScm5fYhBe
x5KJZS8q9ImgJNiwM8yow31CJNr+l/d3pcKl3SoeBv/lUqAI0M1AhsyBkpcRm76W
jVOD+BYCV8E3q2hx/3aWOcJdPAsS9rb3qBBsuMgdl2NI+pidGr9+Xpwc+Xmk7KwI
9bKhS3ecVs0ujmNlpzKcNbIWbmpnJFEMwEPoLFdb+i4eZqzlDLAaag07wEL6akEl
OJ421YHqFZe3oJLNjV+6BzswOweA5qiH3TAoRR1gLQKCAQEAnvnMuk38Do3APLC9
wKxpyFuDSkJefJ66GYg15N/s+naJhD3NQpiyhB2FSUTYixhlKiloe9LBmEVR8+v8
HUuXNgn5A/VTmz69oyP/475JaxOoxD2kngWgsoutNk7UY5EFatB6Vt6yYG7iTi6W
UPFKGoTGdEog7gvZKtEdbeK53VbAB9Oi+I4dkPEivvAD4Lx4yYfIxQU5YhfFBYDD
dFYQpUghDXd0eXec89VBWZVTeCRUOC4zCv3CxT6RX++Ok1NGqGLYAwist3TIaaZ+
pV9WQEx+fmEkkDb1+k0pVTCpqwGRG0PojLzZpXgz1Q8tY1Ac7vAyzCJVnT+blb/K
GstQGwKCAQA14kIQkDmVr+XrtxuDgrCS0UEylJXxUS3A3uZaogttumrIwcxMew+s
HPO6Gjw6HXFWvffC5tXaxCHRKQwzXurdLnlZ6WVnSLDEjBGgt0skGkeQPuVs2fRR
w1aHgHM9EjZ2IZiJLu8sdkLGyftwax2ob5njUpmNk54/EBQhlHLyqEJwH2zcxBIL
idjGZ5qZuCmOcIRV2iVWyOc4owX+6tUg+Mb2SrJilovUpXKkwfUNRi1B2Zfn7rMc
5NsbAJsIUARciF+tboYze+synUAw7wVq3ZUqU28InA+CsP4dkiKlqOa6fS89jj64
CWfQimuhwNRb5YQFizsp2IHP1gc7bVyhAoIBADuUUe4J/SH6GqweX6IcuAmOgcio
o5kMr6EM5g8Ly3P1FgeshlK4Jyt+ufKD6yr91Td7TBIa8xnPHlnU3Rji0O4F/jyn
DMIcdFVQkbXtWum/NrprWiDAaWKl1JtmKs+l8fpeL/V8pbjlFx5A7H5qjlTswfq+
vsbrNUkA1DB4yyq8DJzgkVT3LpD14dvROSI2mAJRd8aaGEBmvNX/pJF2cjevmwnz
HB1AU2Pex0yCBi17FYe+72SG9XHR3u4CZ1gT+X62v0TsuKa6WMneVkMraB3M2ltO
f2WuVcay35mPGAnKjvSuj4j3NvgeELmSo97CC5A/bc2d4pDjmb1pkYbrOiQ=
-----END RSA PRIVATE KEY-----

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,6 @@
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDAVVKPnV+KoCmQRq1zGg6n5PjjBFZdVPcKi9fNe78ZqMAMfLSfycPcS
e6HJVt8ylCegBwYFK4EEACKhZANiAAQhWSM0kbB/cXRdYkfvJBJW3G6gGJ2Lkk2x
DsWHERBxkSXdv7/WIXrRMFjZiLorAm4DqcgTvc8hcbJ82FOHDgPwKJltpyRD+5+D
PYPK/HJYUM/MuvtNd4z/TBKn+KajFZ4=
-----END EC PRIVATE KEY-----

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