diff options
| author | Jeremy Clerc <jclerc@google.com> | 2017-02-15 14:29:43 +0100 |
|---|---|---|
| committer | Jeremy Clerc <jclerc@google.com> | 2017-02-15 14:29:43 +0100 |
| commit | 51e2e81502cdab076a3c0e4958fbb58884418fc8 (patch) | |
| tree | 76d94f65b7b3814d0c5e335ec7e6952adf259647 | |
| parent | 1d7c7f7b897ad203f8d873d6338edd7ec735f936 (diff) | |
| download | easypki-51e2e81502cdab076a3c0e4958fbb58884418fc8.tar.xz | |
Add client authentication example.
| -rw-r--r-- | example/client-auth/README.md | 59 | ||||
| -rw-r--r-- | example/client-auth/build-pki.go | 82 | ||||
| -rw-r--r-- | example/client-auth/get.go | 85 | ||||
| -rw-r--r-- | example/client-auth/nginx.conf | 29 | ||||
| -rw-r--r-- | example/client-auth/pki.yaml | 33 | ||||
| -rw-r--r-- | example/env.sh | 2 |
6 files changed, 289 insertions, 1 deletions
diff --git a/example/client-auth/README.md b/example/client-auth/README.md new file mode 100644 index 0000000..b359ace --- /dev/null +++ b/example/client-auth/README.md @@ -0,0 +1,59 @@ +# Client authentication + +In this example, we generate a PKI based on a yaml definition, then we +provision a nginx server that will only allow connections from clients +having a trusted certificate. + +Build the PKI from the yaml definition: + +``` +go run build-pki.go -config_path pki.yaml -db_path pki.boltdb +``` + +Fetch the certificates needed for nginx: + +``` +go run get.go -db_path pki.boltdb -ca_name "Admins Intermediate CA" -bundle_name "localhost" +go run get.go -db_path pki.boltdb -bundle_name "Admins Intermediate CA" +``` + +Create the nginx config structure: + +``` +mkdir conf.d +cp nginx.conf conf.d/ +mv localhost+chain.crt localhost.key conf.d/ +mv Admins\ Intermediate\ CA+chain.crt conf.d/trusted+chain.crt +``` + +To import the client certs in a browser we need a pkcs12 file, unfortunately +golang.org/x/crypto/pkcs12 only provides decoding, so we use openssl. + +Fetch the client certificate and create a pkcs12 formatted file: + +``` +go run get.go -db_path pki.boltdb -ca_name "Admins Intermediate CA" -bundle_name bob@acme.com -full_chain=false +cat bob@acme.com.{key,crt} | openssl pkcs12 -export -out bob@acme.com+pkcs12.crt +``` + +Import bob@acme.com+pkcs12.crt in your favorite browser. + +Fetch the root CA to import in the browser: + +``` +go run get.go -db_path pki.boltdb -bundle_name "CA" +``` + +Import CA+chain.crt in your favorite browser. + +Run nginx: + +``` +docker run --rm -v $PWD/conf.d:/etc/nginx/conf.d -p 8080:443 nginx +``` + +Open you browser at https://localhost:8080, and you should see "Welcome to +nginx!". + +Try to remove your client certificate from your browser and you get 400 bad +request. diff --git a/example/client-auth/build-pki.go b/example/client-auth/build-pki.go new file mode 100644 index 0000000..63dfefc --- /dev/null +++ b/example/client-auth/build-pki.go @@ -0,0 +1,82 @@ +package main + +import ( + "crypto/x509/pkix" + "flag" + "io/ioutil" + "log" + + "crypto/x509" + "time" + + "github.com/boltdb/bolt" + "github.com/go-yaml/yaml" + "github.com/google/easypki/pkg/certificate" + "github.com/google/easypki/pkg/easypki" + "github.com/google/easypki/pkg/store" +) + +type configCerts struct { + Name string `yaml:"name"` + CommonName string `yaml:"commonName"` + DNSNames []string `yaml:"dnsNames"` + EmailAddresses []string `yaml:"emailAddresses"` + IsCA bool `yaml:"isCA"` + IsClient bool `yaml:"isClient"` + Signer string `yaml:"signer"` + Expire time.Duration `yaml:"expire"` +} + +type config struct { + Subject pkix.Name `yaml:"subject"` + Certs []configCerts `yaml:"certs"` +} + +func main() { + var ( + configPath = flag.String("config_path", "chain.yaml", "Configuration path to generate PKI.") + dbPath = flag.String("db_path", "", "Bolt database path.") + ) + flag.Parse() + b, err := ioutil.ReadFile(*configPath) + if err != nil { + log.Fatalf("Failed reading configuration file %v: %v", *configPath, err) + } + conf := &config{} + if err := yaml.Unmarshal(b, conf); err != nil { + log.Fatalf("Failed umarshaling yaml config (%v) %v: %v", *configPath, string(b), err) + } + db, err := bolt.Open(*dbPath, 0600, nil) + if err != nil { + log.Fatalf("Failed opening bolt database %v: %v", *dbPath, err) + } + defer db.Close() + pki := &easypki.EasyPKI{Store: &store.Bolt{DB: db}} + for _, cert := range conf.Certs { + req := &easypki.Request{ + Name: cert.Name, + Template: &x509.Certificate{ + Subject: conf.Subject, + NotAfter: time.Now().Add(cert.Expire), + IsCA: cert.IsCA, + DNSNames: cert.DNSNames, + EmailAddresses: cert.EmailAddresses, + }, + IsClientCertificate: cert.IsClient, + } + if cert.IsCA { + req.Template.MaxPathLen = -1 + } + req.Template.Subject.CommonName = cert.CommonName + var signer *certificate.Bundle + if cert.Signer != "" { + signer, err = pki.GetCA(cert.Signer) + if err != nil { + log.Fatalf("Cannot sign %v because cannot get CA %v: %v", cert.Name, cert.Signer, err) + } + } + if err := pki.Sign(signer, req); err != nil { + log.Fatalf("Cannot create bundle for %v: %v", cert.Name, err) + } + } +} diff --git a/example/client-auth/get.go b/example/client-auth/get.go new file mode 100644 index 0000000..a252216 --- /dev/null +++ b/example/client-auth/get.go @@ -0,0 +1,85 @@ +package main + +import ( + "encoding/pem" + "flag" + "log" + + "os" + + "crypto/x509" + + "github.com/boltdb/bolt" + "github.com/google/easypki/pkg/certificate" + "github.com/google/easypki/pkg/easypki" + "github.com/google/easypki/pkg/store" +) + +func main() { + var ( + caName = flag.String("ca_name", "", "Name of the CA which signed the bundle.") + bundleName = flag.String("bundle_name", "", "Name of the bundle to retrieve.") + fullChain = flag.Bool("full_chain", true, "Include chain of trust in certificate output.") + dbPath = flag.String("db_path", "", "Bolt database path.") + ) + flag.Parse() + if *bundleName == "" { + log.Fatal("bundle_name cannot be empty") + } + db, err := bolt.Open(*dbPath, 0600, nil) + if err != nil { + log.Fatalf("Failed opening bolt database %v: %v", *dbPath, err) + } + defer db.Close() + pki := &easypki.EasyPKI{Store: &store.Bolt{DB: db}} + + var bundle *certificate.Bundle + if *caName == "" { + *caName = *bundleName + } + bundle, err = pki.GetBundle(*caName, *bundleName) + if err != nil { + log.Fatalf("Failed getting bundle %v within CA %v: %v", *bundleName, *caName, err) + } + leaf := bundle + chain := []*certificate.Bundle{bundle} + if *fullChain { + for { + if leaf.Cert.Issuer.CommonName == leaf.Cert.Subject.CommonName { + break + } + ca, err := pki.GetCA(leaf.Cert.Issuer.CommonName) + if err != nil { + log.Fatalf("Failed getting signing CA %v: %v", leaf.Cert.Issuer.CommonName, err) + } + chain = append(chain, ca) + leaf = ca + } + } + key, err := os.Create(*bundleName + ".key") + if err != nil { + log.Fatalf("Failed creating key output file: %v", err) + } + if err := pem.Encode(key, &pem.Block{ + Bytes: x509.MarshalPKCS1PrivateKey(bundle.Key), + Type: "RSA PRIVATE KEY", + }); err != nil { + log.Fatalf("Failed ecoding private key: %v", err) + } + crtName := *bundleName + ".crt" + if *fullChain { + crtName = *bundleName + "+chain.crt" + } + cert, err := os.Create(crtName) + if err != nil { + log.Fatalf("Failed creating chain output file: %v", err) + } + for _, c := range chain { + if err := pem.Encode(cert, &pem.Block{ + Bytes: c.Cert.Raw, + Type: "CERTIFICATE", + }); err != nil { + log.Fatalf("Failed ecoding %v certificate: %v", c.Name, err) + } + } +} diff --git a/example/client-auth/nginx.conf b/example/client-auth/nginx.conf new file mode 100644 index 0000000..8a27f9b --- /dev/null +++ b/example/client-auth/nginx.conf @@ -0,0 +1,29 @@ +server { + listen 443 ssl; + server_name localhost; + keepalive_timeout 70; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5; + ssl_certificate /etc/nginx/conf.d/localhost+chain.crt; + ssl_certificate_key /etc/nginx/conf.d/localhost.key; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + + ssl_client_certificate /etc/nginx/conf.d/trusted+chain.crt; + ssl_verify_depth 2; + + ssl_verify_client on; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/example/client-auth/pki.yaml b/example/client-auth/pki.yaml new file mode 100644 index 0000000..e115c04 --- /dev/null +++ b/example/client-auth/pki.yaml @@ -0,0 +1,33 @@ +subject: + organization: + - "Acme Inc." + organizationalUnit: + - "IT" + locality: + - "Agloe" + country: + - "US" + province: + - "New York" +certs: + - name: "CA" + commonName: "CA" + isCA: true + expire: "720h" + - name: "Admins Intermediate CA" + commonName: "Admins Intermediate CA" + signer: "CA" + isCA: true + expire: "720h" + - name: "localhost" + commonName: "localhost" + dnsNames: + - "localhost" + signer: "Admins Intermediate CA" + expire: "720h" + - name: "bob@acme.com" + commonName: "bob@acme.com" + emailAddresses: + - "bob@acme.com" + signer: "Admins Intermediate CA" + expire: "720h"
\ No newline at end of file diff --git a/example/env.sh b/example/env.sh index ac28064..144fa76 100644 --- a/example/env.sh +++ b/example/env.sh @@ -1,5 +1,5 @@ export PKI_ROOT="/tmp/pki" -export PKI_ORGANIZATION="Umbrella Corp" +export PKI_ORGANIZATION="Acme Inc." export PKI_ORGANIZATIONAL_UNIT="IT" export PKI_COUNTRY="US" export PKI_LOCALITY="Agloe" |
