diff --git a/cmd/deptokens/main.go b/cmd/deptokens/main.go index 35660d9..655444f 100644 --- a/cmd/deptokens/main.go +++ b/cmd/deptokens/main.go @@ -13,11 +13,6 @@ import ( "github.com/micromdm/nanodep/tokenpki" ) -const ( - defaultCN = "deptokens" - defaultDays = 1 -) - // overridden by -ldflags -X var version = "unknown" @@ -25,6 +20,8 @@ func main() { var ( flCert = flag.String("cert", "cert.pem", "path to certificate") flKey = flag.String("key", "cert.key", "path to key") + flCN = flag.String("cn", "deptokens", "common name to use when creating the certificate") + flDays = flag.Int64("days", 1, "validity of the certificate in days") flPassword = flag.String("password", "", "password to encrypt/decrypt private key with") flTokens = flag.String("token", "", "path to tokens") flForce = flag.Bool("f", false, "force overwriting the keypair") @@ -39,10 +36,14 @@ func main() { var err error if *flTokens == "" { + if *flDays <= 0 { + fmt.Println("ERROR: invalid -days flag") + os.Exit(1) + } if *flPassword == "" { fmt.Println("WARNING: no password provided, private key will be saved in clear text") } - err = generateKeyPair(*flCert, *flKey, *flPassword, *flForce) + err = generateKeyPair(*flCert, *flKey, *flPassword, *flForce, *flCN, *flDays) if err == nil { fmt.Printf("wrote %s, %s\n", *flCert, *flKey) } @@ -101,7 +102,7 @@ func decodeEncryptedKeyPEM(pemBytes []byte, password string) (*rsa.PrivateKey, e } // generateKeyPair creates and saves a keypair checking whether they exist first. -func generateKeyPair(certFile, keyFile, password string, force bool) error { +func generateKeyPair(certFile, keyFile, password string, force bool, cn string, days int64) error { if !force { _, err := os.Stat(certFile) certExists := err == nil @@ -111,7 +112,7 @@ func generateKeyPair(certFile, keyFile, password string, force bool) error { return errors.New("cert or key already exist, not overwriting") } } - key, cert, err := tokenpki.SelfSignedRSAKeypair(defaultCN, defaultDays) + key, cert, err := tokenpki.SelfSignedRSAKeypair(cn, days) if err != nil { return fmt.Errorf("generating keypair: %w", err) } diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 86e258b..0b9de1a 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -158,6 +158,19 @@ paths: description: Generate and store a new X.509 certificate and RSA private key (keypair) for exchanging the encrypted DEP OAuth1 tokens via the Apple ABM/ASM/BE portal. Each request generates a new (and overwrites the existing) keypair. The certificate is returned. security: - basicAuth: [] + parameters: + - in: query + name: cn + required: false + schema: + type: string + example: "depserver" + - in: query + name: validity_days + required: false + schema: + type: integer + example: 365 responses: '200': description: X.509 certificate of the keypair used to encrypted the OAuth1 tokens. diff --git a/docs/operations-guide.md b/docs/operations-guide.md index 827449b..9126849 100644 --- a/docs/operations-guide.md +++ b/docs/operations-guide.md @@ -77,7 +77,7 @@ The `/v1/tokenpki/{name}` endpoints deal with the public key exchange using the * Endpoint: `GET, PUT /v1/tokens/{name}` -The `/v1/tokens/{name} ` endpoints deal with the raw DEP OAuth tokens in JSON form. I.e. after the PKI exchange you can query for the actual DEP OAuth tokens if you like. This also allows configuring the OAuth1 tokens for a DEP name if you already have the tokens in JSON format. I.e. if you used the `deptokens` tool or you're using the DEP simulator `depsim`. +The `/v1/tokens/{name}` endpoints deal with the raw DEP OAuth tokens in JSON form. I.e. after the PKI exchange you can query for the actual DEP OAuth tokens if you like. This also allows configuring the OAuth1 tokens for a DEP name if you already have the tokens in JSON format. I.e. if you used the `deptokens` tool or you're using the DEP simulator `depsim`. #### Assigner @@ -169,10 +169,14 @@ The [Quickstart Guide](quickstart.md) also documents some usage of these scripts For the DEP "MDM server" in the environment variable $DEP_NAME (see above) this script generates and retrieves the public key certificate for use when downloading the DEP authentication tokens from the ABM/ASM/BE portal. The `curl` call will dump the PEM-encoded certificate to stdout so you'll likely want to redirect it somewhere useful so it can be uploaded to the portal. +This script has two optional arguments: +- The first argument specifies the Common Name to set in the certificate (default "depserver"). +- The second argument specifies the validity of the certificate in days (default 1 day). + ##### Example usage ```bash -$ ./tools/cfg-get-cert.sh > $DEP_NAME.pem +$ ./tools/cfg-get-cert.sh depserver 365 > $DEP_NAME.pem % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 1001 100 1001 0 0 4509 0 --:--:-- --:--:-- --:--:-- 4509 @@ -520,6 +524,18 @@ The file path to read or save the RSA private key that corresponds to the public A password to encrypt or decrypt RSA private key on disk with. Note this is password is just to protect the private key itself and does not play a role in the token PKI exchange with Apple. +#### -cn + +* common name to set in the certificate + +A Common Name string to set in the certificate (default is "depserver"). + +#### -days + +* validity of the generated certificate in days + +The generated certificate will expire after the provided days. + #### -token string * path to tokens diff --git a/http/api/tokenpki.go b/http/api/tokenpki.go index ca3aae2..b944823 100644 --- a/http/api/tokenpki.go +++ b/http/api/tokenpki.go @@ -9,6 +9,7 @@ import ( "errors" "io" "net/http" + "strconv" "github.com/micromdm/nanodep/client" "github.com/micromdm/nanodep/log" @@ -24,11 +25,6 @@ type TokenPKIStorer interface { StoreTokenPKI(context.Context, string, []byte, []byte) error } -const ( - defaultCN = "depserver" - defaultDays = 1 -) - // PEMRSAPrivateKey returns key as a PEM block. func PEMRSAPrivateKey(key *rsa.PrivateKey) []byte { block := &pem.Block{ @@ -48,6 +44,10 @@ func PEMRSAPrivateKey(key *rsa.PrivateKey) []byte { // errors to the output as this is meant for "API" users. func GetCertTokenPKIHandler(store TokenPKIStorer, logger log.Logger) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + const ( + defaultCN = "depserver" + defaultDays = 1 + ) logger := ctxlog.Logger(r.Context(), logger) if r.URL.Path == "" { logger.Info("msg", "DEP name check", "err", "missing DEP name") @@ -55,7 +55,25 @@ func GetCertTokenPKIHandler(store TokenPKIStorer, logger log.Logger) http.Handle return } logger = logger.With("name", r.URL.Path) - key, cert, err := tokenpki.SelfSignedRSAKeypair(defaultCN, defaultDays) + var validityDays int64 + if daysArg := r.URL.Query().Get("validity_days"); daysArg == "" { + logger.Debug("msg", "using default validity days", "days", defaultDays) + validityDays = defaultDays + } else { + var err error + validityDays, err = strconv.ParseInt(daysArg, 10, 64) + if err != nil { + logger.Info("msg", "validity_days check", "err", err) + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + } + cn := r.URL.Query().Get("cn") + if cn == "" { + logger.Debug("msg", "using default CN", "cn", defaultCN) + cn = defaultCN + } + key, cert, err := tokenpki.SelfSignedRSAKeypair(cn, validityDays) if err != nil { logger.Info("msg", "generating token keypair", "err", err) jsonError(w, err) diff --git a/tokenpki/cert.go b/tokenpki/cert.go index 4f21131..651b334 100644 --- a/tokenpki/cert.go +++ b/tokenpki/cert.go @@ -14,7 +14,7 @@ import ( // SelfSignedRSAKeypair generates a 2048-bit RSA private key and self-signs an // X.509 certificate using it. You can set the Common Name in cn and the // validity duration with days. -func SelfSignedRSAKeypair(cn string, days int) (*rsa.PrivateKey, *x509.Certificate, error) { +func SelfSignedRSAKeypair(cn string, days int64) (*rsa.PrivateKey, *x509.Certificate, error) { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, nil, err diff --git a/tools/cfg-get-cert.sh b/tools/cfg-get-cert.sh index 1226eb9..c29bca6 100755 --- a/tools/cfg-get-cert.sh +++ b/tools/cfg-get-cert.sh @@ -1,6 +1,6 @@ #!/bin/sh -URL="${BASE_URL}/v1/tokenpki/${DEP_NAME}" +URL="${BASE_URL}/v1/tokenpki/${DEP_NAME}?cn=$1&validity_days=$2" curl \ $CURL_OPTS \