Skip to content

Commit

Permalink
update readme/usage
Browse files Browse the repository at this point in the history
  • Loading branch information
groob committed Jun 2, 2016
1 parent 6d44e88 commit 38528d0
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 5 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.DS_Store
cmd/scep/scep
build/
37 changes: 34 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,45 @@
SCEP server and Go library
`scep` is a Simple Certificate Enrollment Protocol server.

# Standalone server and client binaries
A standalone go server is available under `cmd/scep/main.go`
# Installation
A binary release is available on the releases page.

# Usage

The default flags configure and run the scep server.
depot must be the path to a folder with `ca.pem` and `ca.key` files.

If you don't already have a CA to use, you can create one using the `scep ca` subcommand.

```
Usage of ./cmd/scep/scep:
-challenge string
enforce a challenge password
-depot string
path to ca folder (default "depot")
-port string
port to listen on (default "8080")
-version
prints version information
```

`scep ca -init` to create a new CA and private key.

```
Usage of ./cmd/scep/scep ca:
-country string
country for CA cert (default "US")
-depot string
path to ca folder (default "depot")
-init
create a new CA
-key-password string
password to store rsa key
-keySize int
rsa key size (default 4096)
-organization string
organization for CA cert (default "scep-ca")
-years int
default CA years (default 10)
```

# SCEP library
Expand Down
227 changes: 226 additions & 1 deletion cmd/scep/main.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
package main

import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"errors"
"flag"
"fmt"
"math/big"
"net/http"
"os"
"os/signal"
"syscall"
"time"

"github.com/go-kit/kit/log"
"github.com/micromdm/scep/server"
Expand All @@ -20,13 +31,30 @@ var (
)

func main() {
// flags
var caCMD = flag.NewFlagSet("ca", flag.ExitOnError)
{
if len(os.Args) >= 1 {
if os.Args[1] == "ca" {
status := caMain(caCMD)
os.Exit(status)
}
}
}

//main flags
var (
flVersion = flag.Bool("version", false, "prints version information")
flPort = flag.String("port", envString("SCEP_HTTP_LISTEN_PORT", "8080"), "port to listen on")
flDepotPath = flag.String("depot", envString("SCEP_FILE_DEPOT", "depot"), "path to ca folder")
flChallengePassword = flag.String("challenge", envString("SCEP_CHALLENGE_PASSWORD", ""), "enforce a challenge password")
)
flag.Usage = func() {
flag.PrintDefaults()

fmt.Println("usage: scep [<command>] [<args>]")
fmt.Println(" ca <args> create/manage a CA")
fmt.Println("type <command> --help to see usage for each subcommand")
}
flag.Parse()

// print version information
Expand Down Expand Up @@ -88,6 +116,203 @@ func main() {
logger.Log("terminated", <-errs)
}

func caMain(cmd *flag.FlagSet) int {
var (
flDepotPath = cmd.String("depot", "depot", "path to ca folder")
flInit = cmd.Bool("init", false, "create a new CA")
flYears = cmd.Int("years", 10, "default CA years")
flKeySize = cmd.Int("keySize", 4096, "rsa key size")
flOrg = cmd.String("organization", "scep-ca", "organization for CA cert")
flPassword = cmd.String("key-password", "", "password to store rsa key")
flCountry = cmd.String("country", "US", "country for CA cert")
)
cmd.Parse(os.Args[2:])
if *flInit {
fmt.Println("Initializing new CA")
key, err := createKey(*flKeySize, []byte(*flPassword), *flDepotPath)
if err != nil {
fmt.Println(err)
return 1
}
if err := createCertificateAuthority(key, *flYears, *flOrg, *flCountry, *flDepotPath); err != nil {
fmt.Println(err)
return 1
}
}

return 0
}

// create a key, save it to depot and return it for further usage
func createKey(bits int, password []byte, depot string) (*rsa.PrivateKey, error) {
key, err := newRSAKey(bits)
if err != nil {
return nil, err
}
e, err := encryptedKey(key, password)
if err != nil {
return nil, err
}

if err := os.MkdirAll(depot, 0755); err != nil {
return nil, err
}

name := depot + "/" + "ca.key"
file, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0400)
if err != nil {
return nil, err
}
defer file.Close()

if _, err := file.Write(e); err != nil {
file.Close()
os.Remove(name)
return nil, err
}

return key, nil
}

func createCertificateAuthority(key *rsa.PrivateKey, years int, organization string, country string, depot string) error {
var (
authPkixName = pkix.Name{
Country: nil,
Organization: nil,
OrganizationalUnit: []string{"SCEP CA"},
Locality: nil,
Province: nil,
StreetAddress: nil,
PostalCode: nil,
SerialNumber: "",
CommonName: "",
}
// Build CA based on RFC5280
authTemplate = x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: authPkixName,
// NotBefore is set to be 10min earlier to fix gap on time difference in cluster
NotBefore: time.Now().Add(-600).UTC(),
NotAfter: time.Time{},
// Used for certificate signing only
KeyUsage: x509.KeyUsageCertSign,

ExtKeyUsage: nil,
UnknownExtKeyUsage: nil,

// activate CA
BasicConstraintsValid: true,
IsCA: true,
// Not allow any non-self-issued intermediate CA
MaxPathLen: 0,

// 160-bit SHA-1 hash of the value of the BIT STRING subjectPublicKey
// (excluding the tag, length, and number of unused bits)
// **SHOULD** be filled in later
SubjectKeyId: nil,

// Subject Alternative Name
DNSNames: nil,

PermittedDNSDomainsCritical: false,
PermittedDNSDomains: nil,
}
)

subjectKeyID, err := generateSubjectKeyID(&key.PublicKey)
if err != nil {
return err
}
authTemplate.SubjectKeyId = subjectKeyID
authTemplate.NotAfter = time.Now().AddDate(years, 0, 0).UTC()
authTemplate.Subject.Country = []string{country}
authTemplate.Subject.Organization = []string{organization}
crtBytes, err := x509.CreateCertificate(rand.Reader, &authTemplate, &authTemplate, &key.PublicKey, key)
if err != nil {
return err
}

name := depot + "/" + "ca.pem"
file, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0400)
if err != nil {
return err
}
defer file.Close()

if _, err := file.Write(pemCert(crtBytes)); err != nil {
file.Close()
os.Remove(name)
return err
}

return nil
}

const (
rsaPrivateKeyPEMBlockType = "RSA PRIVATE KEY"
certificatePEMBlockType = "CERTIFICATE"
)

// rsaPublicKey reflects the ASN.1 structure of a PKCS#1 public key.
type rsaPublicKey struct {
N *big.Int
E int
}

// GenerateSubjectKeyID generates SubjectKeyId used in Certificate
// ID is 160-bit SHA-1 hash of the value of the BIT STRING subjectPublicKey
func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) {
var pubBytes []byte
var err error
switch pub := pub.(type) {
case *rsa.PublicKey:
pubBytes, err = asn1.Marshal(rsaPublicKey{
N: pub.N,
E: pub.E,
})
if err != nil {
return nil, err
}
default:
return nil, errors.New("only RSA public key is supported")
}

hash := sha1.Sum(pubBytes)

return hash[:], nil
}

func pemCert(derBytes []byte) []byte {
pemBlock := &pem.Block{
Type: certificatePEMBlockType,
Headers: nil,
Bytes: derBytes,
}
out := pem.EncodeToMemory(pemBlock)
return out
}

// protect an rsa key with a password
func encryptedKey(key *rsa.PrivateKey, password []byte) ([]byte, error) {
privBytes := x509.MarshalPKCS1PrivateKey(key)
privPEMBlock, err := x509.EncryptPEMBlock(rand.Reader, rsaPrivateKeyPEMBlockType, privBytes, password, x509.PEMCipher3DES)
if err != nil {
return nil, err
}

out := pem.EncodeToMemory(privPEMBlock)
return out, nil
}

// create a new RSA private key
func newRSAKey(bits int) (*rsa.PrivateKey, error) {
private, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, err
}
return private, nil
}

func envString(key, def string) string {
if env := os.Getenv(key); env != "" {
return env
Expand Down
18 changes: 18 additions & 0 deletions cmd/scep/release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

VERSION="0.1.0.0"
NAME=scep
OUTPUT=../../build

echo "Building $NAME version $VERSION"

mkdir -p ${OUTPUT}

build() {
echo -n "=> $1-$2: "
GOOS=$1 GOARCH=$2 go build -o ${OUTPUT}/$NAME-$1-$2 -ldflags "-X main.version=$VERSION -X main.gitHash=`git rev-parse HEAD`" ./*.go
du -h ${OUTPUT}/${NAME}-$1-$2
}

build "darwin" "amd64"
build "linux" "amd64"
3 changes: 2 additions & 1 deletion server/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,16 @@ func (svc service) PKIOperation(ctx context.Context, data []byte) ([]byte, error
if err != nil {
return nil, err
}

// create cert template
tmpl := &x509.Certificate{
SerialNumber: serial,
Subject: csr.Subject,
NotBefore: time.Now().Add(-600).UTC(),
NotAfter: time.Now().AddDate(1, 0, 0).UTC(),
SubjectKeyId: id,
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageAny,
x509.ExtKeyUsageClientAuth,
},
}
Expand Down

0 comments on commit 38528d0

Please sign in to comment.