forked from TrueCloudLab/certificates
commit
4518be9fd9
13 changed files with 888 additions and 12 deletions
114
ca/bootstrap.go
Normal file
114
ca/bootstrap.go
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
package ca
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/smallstep/cli/jose"
|
||||||
|
"gopkg.in/square/go-jose.v2/jwt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tokenClaims struct {
|
||||||
|
SHA string `json:"sha"`
|
||||||
|
jose.Claims
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bootstrap is a helper function that initializes a client with the
|
||||||
|
// configuration in the bootstrap token.
|
||||||
|
func Bootstrap(token string) (*Client, error) {
|
||||||
|
tok, err := jwt.ParseSigned(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error parsing token")
|
||||||
|
}
|
||||||
|
var claims tokenClaims
|
||||||
|
if err := tok.UnsafeClaimsWithoutVerification(&claims); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error parsing token")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate bootstrap token
|
||||||
|
switch {
|
||||||
|
case len(claims.SHA) == 0:
|
||||||
|
return nil, errors.New("invalid bootstrap token: sha claim is not present")
|
||||||
|
case !strings.HasPrefix(strings.ToLower(claims.Audience[0]), "http"):
|
||||||
|
return nil, errors.New("invalid bootstrap token: aud claim is not a url")
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewClient(claims.Audience[0], WithRootSHA256(claims.SHA))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootstrapServer is a helper function that returns an http.Server configured
|
||||||
|
// with the given address and handler, and prepared to use TLS connections. The
|
||||||
|
// certificate will automatically rotate if necessary.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// srv, err := ca.BootstrapServer(":443", token, handler)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// srv.ListenAndServeTLS("", "")
|
||||||
|
func BootstrapServer(addr, token string, handler http.Handler) (*http.Server, error) {
|
||||||
|
client, err := Bootstrap(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, pk, err := CreateSignRequest(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sign, err := client.Sign(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig, err := client.GetServerTLSConfig(context.Background(), sign, pk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Handler: handler,
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootstrapClient is a helper function that using the given bootstrap token
|
||||||
|
// return an http.Client configured with a Transport prepared to do TLS
|
||||||
|
// connections using the client certificate returned by the certificate
|
||||||
|
// authority. The certificate will automatically rotate if necessary.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// client, err := ca.BootstrapClient(token)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// resp, err := client.Get("https://internal.smallstep.com")
|
||||||
|
func BootstrapClient(token string) (*http.Client, error) {
|
||||||
|
client, err := Bootstrap(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, pk, err := CreateSignRequest(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sign, err := client.Sign(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
transport, err := client.Transport(context.Background(), sign, pk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
}, nil
|
||||||
|
}
|
219
ca/bootstrap_test.go
Normal file
219
ca/bootstrap_test.go
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
package ca
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/smallstep/certificates/api"
|
||||||
|
"github.com/smallstep/certificates/authority"
|
||||||
|
|
||||||
|
"github.com/smallstep/cli/crypto/randutil"
|
||||||
|
stepJOSE "github.com/smallstep/cli/jose"
|
||||||
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
"gopkg.in/square/go-jose.v2/jwt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func startCABootstrapServer() *httptest.Server {
|
||||||
|
config, err := authority.LoadConfiguration("testdata/ca.json")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
srv := httptest.NewUnstartedServer(nil)
|
||||||
|
config.Address = srv.Listener.Addr().String()
|
||||||
|
ca, err := New(config)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
srv.Config.Handler = ca.srv.Handler
|
||||||
|
srv.TLS = ca.srv.TLSConfig
|
||||||
|
srv.StartTLS()
|
||||||
|
// Force the use of GetCertificate on IPs
|
||||||
|
srv.TLS.Certificates = nil
|
||||||
|
return srv
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateBootstrapToken(ca, subject, sha string) string {
|
||||||
|
now := time.Now()
|
||||||
|
jwk, err := stepJOSE.ParseKey("testdata/secrets/ott_mariano_priv.jwk", stepJOSE.WithPassword([]byte("password")))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
opts := new(jose.SignerOptions).WithType("JWT").WithHeader("kid", jwk.KeyID)
|
||||||
|
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, opts)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
id, err := randutil.ASCII(64)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
cl := struct {
|
||||||
|
SHA string `json:"sha"`
|
||||||
|
jwt.Claims
|
||||||
|
}{
|
||||||
|
SHA: sha,
|
||||||
|
Claims: jwt.Claims{
|
||||||
|
ID: id,
|
||||||
|
Subject: subject,
|
||||||
|
Issuer: "mariano",
|
||||||
|
NotBefore: jwt.NewNumericDate(now),
|
||||||
|
Expiry: jwt.NewNumericDate(now.Add(time.Minute)),
|
||||||
|
Audience: []string{ca + "/sign"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBootstrap(t *testing.T) {
|
||||||
|
srv := startCABootstrapServer()
|
||||||
|
defer srv.Close()
|
||||||
|
token := generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
|
||||||
|
client, err := NewClient(srv.URL+"/sign", WithRootFile("testdata/secrets/root_ca.crt"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
token string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *Client
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok", args{token}, client, false},
|
||||||
|
{"token err", args{"badtoken"}, nil, true},
|
||||||
|
{"bad claims", args{"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.foo.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"}, nil, true},
|
||||||
|
{"bad sha", args{generateBootstrapToken(srv.URL, "subject", "")}, nil, true},
|
||||||
|
{"bad aud", args{generateBootstrapToken("", "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")}, nil, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := Bootstrap(tt.args.token)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Bootstrap() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tt.wantErr {
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Bootstrap() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if got == nil {
|
||||||
|
t.Error("Bootstrap() = nil, want not nil")
|
||||||
|
} else {
|
||||||
|
if !reflect.DeepEqual(got.endpoint, tt.want.endpoint) {
|
||||||
|
t.Errorf("Bootstrap() endpoint = %v, want %v", got.endpoint, tt.want.endpoint)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got.certPool, tt.want.certPool) {
|
||||||
|
t.Errorf("Bootstrap() certPool = %v, want %v", got.certPool, tt.want.certPool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBootstrapServer(t *testing.T) {
|
||||||
|
srv := startCABootstrapServer()
|
||||||
|
defer srv.Close()
|
||||||
|
token := func() string {
|
||||||
|
return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
|
||||||
|
}
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("ok"))
|
||||||
|
})
|
||||||
|
type args struct {
|
||||||
|
addr string
|
||||||
|
token string
|
||||||
|
handler http.Handler
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok", args{":0", token(), handler}, false},
|
||||||
|
{"fail", args{":0", "bad-token", handler}, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := BootstrapServer(tt.args.addr, tt.args.token, tt.args.handler)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("BootstrapServer() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tt.wantErr {
|
||||||
|
if got != nil {
|
||||||
|
t.Errorf("BootstrapServer() = %v, want nil", got)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !reflect.DeepEqual(got.Addr, tt.args.addr) {
|
||||||
|
t.Errorf("BootstrapServer() Addr = %v, want %v", got.Addr, tt.args.addr)
|
||||||
|
}
|
||||||
|
if got.TLSConfig == nil || got.TLSConfig.ClientCAs == nil || got.TLSConfig.RootCAs == nil || got.TLSConfig.GetCertificate == nil || got.TLSConfig.GetClientCertificate == nil {
|
||||||
|
t.Errorf("BootstrapServer() invalid TLSConfig = %#v", got.TLSConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBootstrapClient(t *testing.T) {
|
||||||
|
srv := startCABootstrapServer()
|
||||||
|
defer srv.Close()
|
||||||
|
token := func() string {
|
||||||
|
return generateBootstrapToken(srv.URL, "subject", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7")
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
token string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok", args{token()}, false},
|
||||||
|
{"fail", args{"bad-token"}, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := BootstrapClient(tt.args.token)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("BootstrapClient() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tt.wantErr {
|
||||||
|
if got != nil {
|
||||||
|
t.Errorf("BootstrapClient() = %v, want nil", got)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tlsConfig := got.Transport.(*http.Transport).TLSClientConfig
|
||||||
|
if tlsConfig == nil || tlsConfig.ClientCAs != nil || tlsConfig.GetClientCertificate == nil || tlsConfig.RootCAs == nil || tlsConfig.GetCertificate != nil {
|
||||||
|
t.Errorf("BootstrapClient() invalid Transport = %#v", tlsConfig)
|
||||||
|
}
|
||||||
|
resp, err := got.Post(srv.URL+"/renew", "application/json", http.NoBody)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("BootstrapClient() failed renewing certificate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var renewal api.SignResponse
|
||||||
|
if err := readJSON(resp.Body, &renewal); err != nil {
|
||||||
|
t.Errorf("BootstrapClient() error reading response: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if renewal.CaPEM.Certificate == nil || renewal.ServerPEM.Certificate == nil {
|
||||||
|
t.Errorf("BootstrapClient() invalid renewal response: %v", renewal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
227
examples/README.md
Normal file
227
examples/README.md
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
## Basic client usage
|
||||||
|
|
||||||
|
The basic-client example shows the use of the most of the functioanlity of the
|
||||||
|
`ca.Client`, those methods works as an SDK for integrating other services with
|
||||||
|
the Certificate Authority (CA).
|
||||||
|
|
||||||
|
In [basic-client/client.go](/examples/basic-client/client.go) we first can see
|
||||||
|
the initialization of the client:
|
||||||
|
|
||||||
|
```go
|
||||||
|
client, err := ca.NewClient("https://localhost:9000", ca.WithRootSHA256("84a033e84196f73bd593fad7a63e509e57fd982f02084359c4e8c5c864efc27d"))
|
||||||
|
```
|
||||||
|
|
||||||
|
The previous code uses the CA address and the root certificate fingerprint.
|
||||||
|
The CA url will be present in the token, and the root fingerprint can be present
|
||||||
|
too if the `--root root_ca.crt` option is use in the creation of the token. If
|
||||||
|
this is the case is simpler to rely in the token and use just:
|
||||||
|
|
||||||
|
```go
|
||||||
|
client, err := ca.Bootstrap(token)
|
||||||
|
```
|
||||||
|
|
||||||
|
After the initialization there're examples of all the client methods, they are
|
||||||
|
just a convenient way to use the CA API endpoints. The first method `Health`
|
||||||
|
returns the status of the CA server, on the first implementation if the server
|
||||||
|
is up it will return just ok.
|
||||||
|
|
||||||
|
```go
|
||||||
|
health, err := client.Health()
|
||||||
|
// Health is a struct created from the JSON response {"status": "ok"}
|
||||||
|
```
|
||||||
|
|
||||||
|
The next method `Root` is used to get and verify the root certificate. We will
|
||||||
|
pass a finger print, it will download the root certificate from the CA and it
|
||||||
|
will make sure that the fingerprint matches. This method uses an insecure HTTP
|
||||||
|
client as it might be used in the initialization of the client, but the response
|
||||||
|
is considered secure because we have compared against the given digest.
|
||||||
|
|
||||||
|
```go
|
||||||
|
root, err := client.Root("84a033e84196f73bd593fad7a63e509e57fd982f02084359c4e8c5c864efc27d")
|
||||||
|
```
|
||||||
|
|
||||||
|
After Root we have the most important method `Sign`, this is used to sign a
|
||||||
|
Certificate Signing Request that we provide. To secure this request we use
|
||||||
|
the one-time-token. You can build your own certificate request and add it in
|
||||||
|
the `*api.SignRequest`, but the ca package contains a method that will create a
|
||||||
|
secure random key, and create the CSR based on the information in the token.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Create a CSR from token and return the sign request, the private key and an
|
||||||
|
// error if something failed.
|
||||||
|
req, pk, err := ca.CreateSignRequest(token)
|
||||||
|
if err != nil { ... }
|
||||||
|
|
||||||
|
// Do the sign request and return the signed certificate
|
||||||
|
sign, err := client.Sign(req)
|
||||||
|
if err != nil { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
To renew the certificate we can use the `Renew` method, the certificate renewal
|
||||||
|
relies on a mTLS connection with a previous certificate, so we will need to pass
|
||||||
|
a transport with the previous certificate.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Get a cancelable context to stop the renewal goroutines and timers.
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
// Create a transport from with the sign response and the private key.
|
||||||
|
tr, err := client.Transport(ctx, sign, pk)
|
||||||
|
if err != nil { ... }
|
||||||
|
// Renew the certificate and get the new ones.
|
||||||
|
// The return type are equivalent to ones in the Sign method.
|
||||||
|
renew, err := client.Renew(tr)
|
||||||
|
if err != nil { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
All the previous methods map with one endpoint in the CA API, but the API
|
||||||
|
provides a couple more that are used for creating the tokens. For those we have
|
||||||
|
a couple of methods, one that returns a list of provisioners and one that
|
||||||
|
returns the encrypted key of one provisioner.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Without options it will return the first 20 provisioners
|
||||||
|
provisioners, err := client.Provisioners()
|
||||||
|
// We can also set a limit up to 100
|
||||||
|
provisioners, err := client.Provisioners(ca.WithProvisionerLimit(100))
|
||||||
|
// With a pagination cursor
|
||||||
|
provisioners, err := client.Provisioners(ca.WithProvisionerCursor("1f18c1ecffe54770e9107ce7b39b39735"))
|
||||||
|
// Or combining both
|
||||||
|
provisioners, err := client.Provisioners(
|
||||||
|
ca.WithProvisionerCursor("1f18c1ecffe54770e9107ce7b39b39735"),
|
||||||
|
ca.WithProvisionerLimit(100),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Return the encrypted key of one of the returned provisioners. The key
|
||||||
|
// returned is an encrypted JWE with the private key used to sign tokens.
|
||||||
|
key, err := client.ProvisionerKey("DmAtZt2EhmZr_iTJJ387fr4Md2NbzMXGdXQNW1UWPXk")
|
||||||
|
```
|
||||||
|
|
||||||
|
The example shows also the use of some helper methods used to get configured
|
||||||
|
tls.Config objects that can be injected in servers and clients. These methods,
|
||||||
|
are also configured to auto-renew the certificate once two thirds of the
|
||||||
|
duration of the certificate has passed, approximately.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Get a cancelable context to stop the renewal goroutines and timers.
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
// Get tls.Config for a server
|
||||||
|
tlsConfig, err := client.GetServerTLSConfig(ctx, sign, pk)
|
||||||
|
// Get tls.Config for a client
|
||||||
|
tlsConfig, err := client.GetClientTLSConfig(ctx, sign, pk)
|
||||||
|
// Get an http.Transport for a client, this can be used as a http.RoundTripper
|
||||||
|
// in an http.Client
|
||||||
|
tr, err := client.Transport(ctx, sign, pk)
|
||||||
|
```
|
||||||
|
|
||||||
|
To run the example you need to start the certificate authority:
|
||||||
|
|
||||||
|
```
|
||||||
|
certificates $ bin/step-ca examples/pki/config/ca.json
|
||||||
|
2018/11/02 18:29:25 Serving HTTPS on :9000 ...
|
||||||
|
```
|
||||||
|
|
||||||
|
And just run the client.go with a new token:
|
||||||
|
```
|
||||||
|
certificates $ export STEPPATH=examples/pki
|
||||||
|
certificates $ export STEP_CA_URL=https://localhost:9000
|
||||||
|
certificates $ go run examples/basic-client/client.go $(step ca new-token client.smallstep.com))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bootstrap Client & Server
|
||||||
|
|
||||||
|
On this example we are going to see the Certificate Authority running, as well
|
||||||
|
as a simple Server using TLS and a simple client doing TLS requests to the
|
||||||
|
server.
|
||||||
|
|
||||||
|
The examples directory already contains a sample pki configuration with the
|
||||||
|
password `password` hardcoded, but you can create your own using `step ca init`.
|
||||||
|
|
||||||
|
First we will start the certificate authority:
|
||||||
|
```
|
||||||
|
certificates $ bin/step-ca examples/pki/config/ca.json
|
||||||
|
2018/11/02 18:29:25 Serving HTTPS on :9000 ...
|
||||||
|
```
|
||||||
|
|
||||||
|
We will start the server and we will type `password` when step asks for the
|
||||||
|
provisioner password:
|
||||||
|
```
|
||||||
|
certificates $ export STEPPATH=examples/pki
|
||||||
|
certificates $ export STEP_CA_URL=https://localhost:9000
|
||||||
|
certificates $ go run examples/bootstrap-server/server.go $(step ca new-token localhost))
|
||||||
|
✔ Key ID: DmAtZt2EhmZr_iTJJ387fr4Md2NbzMXGdXQNW1UWPXk (mariano@smallstep.com)
|
||||||
|
Please enter the password to decrypt the provisioner key:
|
||||||
|
Listening on :8443 ...
|
||||||
|
```
|
||||||
|
|
||||||
|
We try that using cURL with the system certificates it will return an error:
|
||||||
|
```
|
||||||
|
certificates $ curl https://localhost:8443
|
||||||
|
curl: (60) SSL certificate problem: unable to get local issuer certificate
|
||||||
|
More details here: https://curl.haxx.se/docs/sslcerts.html
|
||||||
|
|
||||||
|
curl performs SSL certificate verification by default, using a "bundle"
|
||||||
|
of Certificate Authority (CA) public keys (CA certs). If the default
|
||||||
|
bundle file isn't adequate, you can specify an alternate file
|
||||||
|
using the --cacert option.
|
||||||
|
If this HTTPS server uses a certificate signed by a CA represented in
|
||||||
|
the bundle, the certificate verification probably failed due to a
|
||||||
|
problem with the certificate (it might be expired, or the name might
|
||||||
|
not match the domain name in the URL).
|
||||||
|
If you'd like to turn off curl's verification of the certificate, use
|
||||||
|
the -k (or --insecure) option.
|
||||||
|
HTTPS-proxy has similar options --proxy-cacert and --proxy-insecure.
|
||||||
|
```
|
||||||
|
|
||||||
|
But if we use the root certificate it will properly work:
|
||||||
|
```
|
||||||
|
certificates $ curl --cacert examples/pki/secrets/root_ca.crt https://localhost:8443
|
||||||
|
Hello nobody at 2018-11-03 01:49:25.66912 +0000 UTC!!!
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice that in the response we see `nobody`, this is because the server didn't
|
||||||
|
detected a TLS client configuration.
|
||||||
|
|
||||||
|
But if we the client with the certificate name Mike we'll see:
|
||||||
|
```
|
||||||
|
certificates $ export STEPPATH=examples/pki
|
||||||
|
certificates $ export STEP_CA_URL=https://localhost:9000
|
||||||
|
certificates $ go run examples/bootstrap-client/client.go $(step ca new-token Mike)
|
||||||
|
✔ Key ID: DmAtZt2EhmZr_iTJJ387fr4Md2NbzMXGdXQNW1UWPXk (mariano@smallstep.com)
|
||||||
|
Please enter the password to decrypt the provisioner key:
|
||||||
|
Server responded: Hello Mike at 2018-11-03 01:52:52.678215 +0000 UTC!!!
|
||||||
|
Server responded: Hello Mike at 2018-11-03 01:52:53.681563 +0000 UTC!!!
|
||||||
|
Server responded: Hello Mike at 2018-11-03 01:52:54.682787 +0000 UTC!!!
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Certificate rotation
|
||||||
|
|
||||||
|
We can use the bootstrap-server to demonstrate the certificate rotation. We've
|
||||||
|
added second provisioner to to the ca with the name of `mike@smallstep.com`,
|
||||||
|
this provisioner is configured with a default certificate duration of 2 minutes.
|
||||||
|
If we run the server, and inspect the used certificate, we can verify how it
|
||||||
|
rotates after approximately two thirds of the duration has passed.
|
||||||
|
|
||||||
|
```
|
||||||
|
certificates $ export STEPPATH=examples/pki
|
||||||
|
certificates $ export STEP_CA_URL=https://localhost:9000
|
||||||
|
certificates $ go run examples/bootstrap-server/server.go $(step ca new-token localhost))
|
||||||
|
✔ Key ID: YYNxZ0rq0WsT2MlqLCWvgme3jszkmt99KjoGEJJwAKs (mike@smallstep.com)
|
||||||
|
Please enter the password to decrypt the provisioner key:
|
||||||
|
Listening on :8443 ...
|
||||||
|
```
|
||||||
|
|
||||||
|
In this specific case, the the rotation will happen after 74-80 seconds have
|
||||||
|
passed, the exact formula is 120-120/3-rand(120/20), where rand will return a
|
||||||
|
number between 0 and 6.
|
||||||
|
|
||||||
|
We can use the following command to check the certificate expiration and to make
|
||||||
|
sure the certificate changes after 74-80 seconds.
|
||||||
|
|
||||||
|
```
|
||||||
|
certificates $ step certificate inspect --insecure https://localhost:8443
|
||||||
|
```
|
158
examples/basic-client/client.go
Normal file
158
examples/basic-client/client.go
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/smallstep/certificates/ca"
|
||||||
|
)
|
||||||
|
|
||||||
|
func printResponse(name string, v interface{}) {
|
||||||
|
b, err := json.MarshalIndent(v, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s response:\n%s\n\n", name, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 2 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: %s <token>\n", os.Args[0])
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := os.Args[1]
|
||||||
|
|
||||||
|
// To create the client using ca.NewClient we need:
|
||||||
|
// * The CA address "https://localhost:9000"
|
||||||
|
// * The root certificate fingerprint
|
||||||
|
// 84a033e84196f73bd593fad7a63e509e57fd982f02084359c4e8c5c864efc27d to get
|
||||||
|
// the root fingerprint we can use `step certificate fingerprint root_ca.crt`
|
||||||
|
client, err := ca.NewClient("https://localhost:9000", ca.WithRootSHA256("84a033e84196f73bd593fad7a63e509e57fd982f02084359c4e8c5c864efc27d"))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other ways to initialize the client would be:
|
||||||
|
// * With the Bootstrap functionality (recommended):
|
||||||
|
// client, err := ca.Bootstrap(token)
|
||||||
|
// * Using the root certificate instead of the fingerprint:
|
||||||
|
// client, err := ca.NewClient("https://localhost:9000", ca.WithRootFile("../pki/secrets/root_ca.crt"))
|
||||||
|
|
||||||
|
// Get the health of the CA
|
||||||
|
health, err := client.Health()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
printResponse("Health", health)
|
||||||
|
|
||||||
|
// Get and verify a root CA
|
||||||
|
root, err := client.Root("84a033e84196f73bd593fad7a63e509e57fd982f02084359c4e8c5c864efc27d")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
printResponse("Root", root)
|
||||||
|
|
||||||
|
// We can use ca.CreateSignRequest to generate a new sign request with a
|
||||||
|
// randomly generated key.
|
||||||
|
req, pk, err := ca.CreateSignRequest(token)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
sign, err := client.Sign(req)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
printResponse("Sign", sign)
|
||||||
|
|
||||||
|
// Renew a certificate with a transport that contains the previous
|
||||||
|
// certificate. We should created a context that allows us to finish the
|
||||||
|
// renewal goroutine.∑
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel() // Finish the renewal goroutine
|
||||||
|
tr, err := client.Transport(ctx, sign, pk)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
renew, err := client.Renew(tr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
printResponse("Renew", renew)
|
||||||
|
|
||||||
|
// Get tls.Config for a server
|
||||||
|
ctxServer, cancelServer := context.WithCancel(context.Background())
|
||||||
|
defer cancelServer()
|
||||||
|
tlsConfig, err := client.GetServerTLSConfig(ctxServer, sign, pk)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// An http server will use the tls.Config like:
|
||||||
|
_ = &http.Server{
|
||||||
|
Addr: ":443",
|
||||||
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("Hello world"))
|
||||||
|
}),
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get tls.Config for a client
|
||||||
|
ctxClient, cancelClient := context.WithCancel(context.Background())
|
||||||
|
defer cancelClient()
|
||||||
|
tlsConfig, err = client.GetClientTLSConfig(ctxClient, sign, pk)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// An http.Client will need to create a transport first
|
||||||
|
_ = &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: tlsConfig,
|
||||||
|
// Options set in http.DefaultTransport
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
DualStack: true,
|
||||||
|
}).DialContext,
|
||||||
|
MaxIdleConns: 100,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// But we can just use client.Transport to get the default configuration
|
||||||
|
ctxTransport, cancelTransport := context.WithCancel(context.Background())
|
||||||
|
defer cancelTransport()
|
||||||
|
tr, err = client.Transport(ctxTransport, sign, pk)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// And http.Client will use the transport like
|
||||||
|
_ = &http.Client{
|
||||||
|
Transport: tr,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get provisioners and provisioner keys. In this example we add two
|
||||||
|
// optional arguments with the initial cursor and a limit.
|
||||||
|
//
|
||||||
|
// A server or a client should not need this functionality, they are used to
|
||||||
|
// sign (private key) and verify (public key) tokens. The step cli can be
|
||||||
|
// used for this purpose.
|
||||||
|
provisioners, err := client.Provisioners(ca.WithProvisionerCursor(""), ca.WithProvisionerLimit(100))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
printResponse("Provisioners", provisioners)
|
||||||
|
// Get encrypted key
|
||||||
|
key, err := client.ProvisionerKey("DmAtZt2EhmZr_iTJJ387fr4Md2NbzMXGdXQNW1UWPXk")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
printResponse("Provisioner Key", key)
|
||||||
|
}
|
39
examples/bootstrap-client/client.go
Normal file
39
examples/bootstrap-client/client.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/smallstep/certificates/ca"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 2 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: %s <token>\n", os.Args[0])
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := os.Args[1]
|
||||||
|
|
||||||
|
client, err := ca.BootstrapClient(token)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
resp, err := client.Get("https://localhost:8443")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Server responded: %s\n", b)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
35
examples/bootstrap-server/server.go
Normal file
35
examples/bootstrap-server/server.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/smallstep/certificates/ca"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 2 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: %s <token>\n", os.Args[0])
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := os.Args[1]
|
||||||
|
|
||||||
|
srv, err := ca.BootstrapServer(":8443", token, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
name := "nobody"
|
||||||
|
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
|
||||||
|
name = r.TLS.PeerCertificates[0].Subject.CommonName
|
||||||
|
}
|
||||||
|
w.Write([]byte(fmt.Sprintf("Hello %s at %s!!!", name, time.Now().UTC())))
|
||||||
|
}))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Listening on :8443 ...")
|
||||||
|
if err := srv.ListenAndServeTLS("", ""); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"caPath": "/path/to/intermediate-certificate",
|
|
||||||
"caPrivateKeyPath": "/path/to/intermediate-private-key",
|
|
||||||
"caPasscode": "very-secure-passcode",
|
|
||||||
"listenAddress": "127.0.0.1:9000"
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
country: USA
|
|
||||||
locality: San Francisco
|
|
||||||
organization: smallstep
|
|
||||||
common_name: internal.smallstep.com
|
|
||||||
key_type: rsa
|
|
||||||
rsa_bits: 4096
|
|
58
examples/pki/config/ca.json
Normal file
58
examples/pki/config/ca.json
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"root": "examples/pki/secrets/root_ca.crt",
|
||||||
|
"crt": "examples/pki/secrets/intermediate_ca.crt",
|
||||||
|
"key": "examples/pki/secrets/intermediate_ca_key",
|
||||||
|
"password": "password",
|
||||||
|
"address": ":9000",
|
||||||
|
"dnsNames": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"logger": {
|
||||||
|
"format": "text"
|
||||||
|
},
|
||||||
|
"authority": {
|
||||||
|
"provisioners": [
|
||||||
|
{
|
||||||
|
"name": "mariano@smallstep.com",
|
||||||
|
"type": "jwk",
|
||||||
|
"key": {
|
||||||
|
"use": "sig",
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "DmAtZt2EhmZr_iTJJ387fr4Md2NbzMXGdXQNW1UWPXk",
|
||||||
|
"crv": "P-256",
|
||||||
|
"alg": "ES256",
|
||||||
|
"x": "jXoO1j4CXxoTC32pNzkVC8l6k2LfP0k5ndhJZmcdVbk",
|
||||||
|
"y": "c3JDL4GTFxJWHa8EaHdMh4QgwMh64P2_AGWrD0ADXcI"
|
||||||
|
},
|
||||||
|
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiOTFVWjdzRGw3RlNXcldfX1I1NUh3USJ9.FcWtrBDNgrkA33G9Ll9sXh1cPF-3jVXeYe1FLmSDc_Q2PmfLOPvJOA.0ZoN32ayaRWnufJb.WrkffMmDLWiq1-2kn-w7-kVBGW12gjNCBHNHB1hyEdED0rWH1YWpKd8FjoOACdJyLhSn4kAS3Lw5AH7fvO27A48zzvoxZU5EgSm5HG9IjkIH-LBJ-v79ShkpmPylchgjkFhxa5epD11OIK4rFmI7s-0BCjmJokLR_DZBhDMw2khGnsr_MEOfAz9UnqXaQ4MIy8eT52xUpx68gpWFlz2YP3EqiYyNEv0PpjMtyP5lO2i8-p8BqvuJdus9H3fO5Dg-1KVto1wuqh4BQ2JKTauv60QAnM_4sdxRHku3F_nV64SCrZfDvnN2ve21raFROtyXaqHZhN6lyoPxDncy8v4.biaOblEe0N-gMpJyFZ-3-A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mike@smallstep.com",
|
||||||
|
"type": "jwk",
|
||||||
|
"key": {
|
||||||
|
"use": "sig",
|
||||||
|
"kty": "EC",
|
||||||
|
"kid": "YYNxZ0rq0WsT2MlqLCWvgme3jszkmt99KjoGEJJwAKs",
|
||||||
|
"crv": "P-256",
|
||||||
|
"alg": "ES256",
|
||||||
|
"x": "LsI8nHBflc-mrCbRqhl8d3hSl5sYuSM1AbXBmRfznyg",
|
||||||
|
"y": "F99LoOvi7z-ZkumsgoHIhodP8q9brXe4bhF3szK-c_w"
|
||||||
|
},
|
||||||
|
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiVERQS2dzcEItTUR4ZDJxTGo0VlpwdyJ9.2_j0cZgTm2eFkZ-hrtr1hBIvLxN0w3TZhbX0Jrrq7vBMaywhgFcGTA.mCasZCbZJ-JT7vjA.bW052WDKSf_ueEXq1dyxLq0n3qXWRO-LXr7OzBLdUKWKSBGQrzqS5KJWqdUCPoMIHTqpwYvm-iD6uFlcxKBYxnsAG_hoq_V3icvvwNQQSd_q7Thxr2_KtPIDJWNuX1t5qXp11hkgb-8d5HO93CmN7xNDG89pzSUepT6RYXOZ483mP5fre9qzkfnrjx3oPROCnf3SnIVUvqk7fwfXuniNsg3NrNqncHYUQNReiq3e9I1R60w0ZQTvIReY7-zfiq7iPgVqmu5I7XGgFK4iBv0L7UOEora65b4hRWeLxg5t7OCfUqrS9yxAk8FdjFb9sEfjopWViPRepB0dYPH8dVI.fb6-7XWqp0j6CR9Li0NI-Q",
|
||||||
|
"claims": {
|
||||||
|
"minTLSCertDuration": "60s",
|
||||||
|
"defaultTLSCertDuration": "120s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"cipherSuites": [
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
||||||
|
],
|
||||||
|
"minVersion": 1.2,
|
||||||
|
"maxVersion": 1.2,
|
||||||
|
"renegotiation": false
|
||||||
|
}
|
||||||
|
}
|
12
examples/pki/secrets/intermediate_ca.crt
Normal file
12
examples/pki/secrets/intermediate_ca.crt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBxjCCAWugAwIBAgIQAYoOWhdChUmmKzlc0DWcWDAKBggqhkjOPQQDAjAcMRow
|
||||||
|
GAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTAeFw0xODExMDIyMzU0MTNaFw0yODEw
|
||||||
|
MzAyMzU0MTNaMCQxIjAgBgNVBAMTGVNtYWxsc3RlcCBJbnRlcm1lZGlhdGUgQ0Ew
|
||||||
|
WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASxvIWme8/yDAxkR63KgSYkpN7mHKBH
|
||||||
|
k5c8S+uzba4xWbaxZtEZ9NNhEIAgYFZ9/3ThrzLOsuGwRCvPTaD5iycQo4GGMIGD
|
||||||
|
MA4GA1UdDwEB/wQEAwIBpjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
|
||||||
|
EgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU8dKIy5ZLH2h3ihWgqjcpoo5e
|
||||||
|
q3YwHwYDVR0jBBgwFoAU0IpOvAyBnn9UhDqOQzXnfEU3aYMwCgYIKoZIzj0EAwID
|
||||||
|
SQAwRgIhANXlcktuaEvORhgRvzQ6vVNgvpqCEXW3CcCHjUl1xSdaAiEAmakkpfFq
|
||||||
|
VsT5PqPnTRgOWlFESRhQ9btl6nQ+2Lt/S5A=
|
||||||
|
-----END CERTIFICATE-----
|
8
examples/pki/secrets/intermediate_ca_key
Normal file
8
examples/pki/secrets/intermediate_ca_key
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
Proc-Type: 4,ENCRYPTED
|
||||||
|
DEK-Info: AES-256-CBC,4c7758e66df1884f6560839de64d4dd3
|
||||||
|
|
||||||
|
S8Ha8uA+bA3IGPurYODwd9VaJZ6FHI2tlznHXCOxT1MlGqyEAc4aWS11QBUz0Ucp
|
||||||
|
excwlqM8kfh5BcN5a+vvInHnv74ZiNPdpt/apzz2LIx52pApzASiKVXRsAUmR4Pv
|
||||||
|
3MsO1/cVHkilpee1uC+axL32d5YmyP0URpSNJK9BhZo=
|
||||||
|
-----END EC PRIVATE KEY-----
|
10
examples/pki/secrets/root_ca.crt
Normal file
10
examples/pki/secrets/root_ca.crt
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBfDCCASGgAwIBAgIQY0CXerxuM+EhTbpVxxLRKjAKBggqhkjOPQQDAjAcMRow
|
||||||
|
GAYDVQQDExFTbWFsbHN0ZXAgUm9vdCBDQTAeFw0xODExMDIyMzU0MTNaFw0yODEw
|
||||||
|
MzAyMzU0MTNaMBwxGjAYBgNVBAMTEVNtYWxsc3RlcCBSb290IENBMFkwEwYHKoZI
|
||||||
|
zj0CAQYIKoZIzj0DAQcDQgAEEGa7ZeL4WVIfPFDS7glJkIVsITVQgjfyz+AhcYaS
|
||||||
|
rkJZlWOGZ60br9uE/wEfUcX1zavrX1Wz+bSJzTvT0AVBNqNFMEMwDgYDVR0PAQH/
|
||||||
|
BAQDAgGmMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFNCKTrwMgZ5/VIQ6
|
||||||
|
jkM153xFN2mDMAoGCCqGSM49BAMCA0kAMEYCIQCRA4EdlTTMhs2Zd1cT75ZgxeGa
|
||||||
|
mjVPl1vqBxLkHqEO+QIhAPKVm7E452ZBe2o5rQRxGwa94MI+CyuEIH9md3nTgWWX
|
||||||
|
-----END CERTIFICATE-----
|
8
examples/pki/secrets/root_ca_key
Normal file
8
examples/pki/secrets/root_ca_key
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
Proc-Type: 4,ENCRYPTED
|
||||||
|
DEK-Info: AES-256-CBC,98fdc560ba714aebb9fd4b714395d8ce
|
||||||
|
|
||||||
|
2bFn8yRb8lMvDR6oh22PocfhXdaoVNt4QwHCJNy0K0fG8CMokwDfEec//LseP6rA
|
||||||
|
7/EV11+ZgoN9xyTNe1kB6zFv7/kzCpRm23sqtyio+8xXWnLZNYKBRYYEeJWBUqqd
|
||||||
|
GAfazg4ZFzoIH5TEPWCEAp7M9CVvtiw1SeA/zjewp2k=
|
||||||
|
-----END EC PRIVATE KEY-----
|
Loading…
Reference in a new issue