func (b *backend) pathLogin( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { var matched *ParsedCert if verifyResp, resp, err := b.verifyCredentials(req); err != nil { return nil, err } else if resp != nil { return resp, nil } else { matched = verifyResp } if matched == nil { return nil, nil } ttl := matched.Entry.TTL if ttl == 0 { ttl = b.System().DefaultLeaseTTL() } clientCerts := req.Connection.ConnState.PeerCertificates if len(clientCerts) == 0 { return logical.ErrorResponse("no client certificate found"), nil } skid := base64.StdEncoding.EncodeToString(clientCerts[0].SubjectKeyId) akid := base64.StdEncoding.EncodeToString(clientCerts[0].AuthorityKeyId) // Generate a response resp := &logical.Response{ Auth: &logical.Auth{ InternalData: map[string]interface{}{ "subject_key_id": skid, "authority_key_id": akid, }, Policies: matched.Entry.Policies, DisplayName: matched.Entry.DisplayName, Metadata: map[string]string{ "cert_name": matched.Entry.Name, "common_name": clientCerts[0].Subject.CommonName, "subject_key_id": certutil.GetOctalFormatted(clientCerts[0].SubjectKeyId, ":"), "authority_key_id": certutil.GetOctalFormatted(clientCerts[0].AuthorityKeyId, ":"), }, LeaseOptions: logical.LeaseOptions{ Renewable: true, TTL: ttl, }, }, } return resp, nil }
// Generates steps to test out CA configuration -- certificates + CRL expiry, // and ensure that the certificates are readable after storing them func generateCATestingSteps(t *testing.T, caCert, caKey, otherCaCert string, intdata, reqdata map[string]interface{}) []logicaltest.TestStep { ret := []logicaltest.TestStep{ logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "config/ca", Data: map[string]interface{}{ "pem_bundle": caKey + caCert, }, }, logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "config/crl", Data: map[string]interface{}{ "expiry": "16h", }, }, // Ensure we can fetch it back via unauthenticated means, in various formats logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "cert/ca", Unauthenticated: true, Check: func(resp *logical.Response) error { if resp.Data["certificate"].(string) != caCert { return fmt.Errorf("CA certificate:\n%s\ndoes not match original:\n%s\n", resp.Data["certificate"].(string), caCert) } return nil }, }, logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "ca/pem", Unauthenticated: true, Check: func(resp *logical.Response) error { rawBytes := resp.Data["http_raw_body"].([]byte) if string(rawBytes) != caCert { return fmt.Errorf("CA certificate:\n%s\ndoes not match original:\n%s\n", string(rawBytes), caCert) } if resp.Data["http_content_type"].(string) != "application/pkix-cert" { return fmt.Errorf("Expected application/pkix-cert as content-type, but got %s", resp.Data["http_content_type"].(string)) } return nil }, }, logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "ca", Unauthenticated: true, Check: func(resp *logical.Response) error { rawBytes := resp.Data["http_raw_body"].([]byte) pemBytes := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: rawBytes, }) if string(pemBytes) != caCert { return fmt.Errorf("CA certificate:\n%s\ndoes not match original:\n%s\n", string(pemBytes), caCert) } if resp.Data["http_content_type"].(string) != "application/pkix-cert" { return fmt.Errorf("Expected application/pkix-cert as content-type, but got %s", resp.Data["http_content_type"].(string)) } return nil }, }, logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "config/crl", Check: func(resp *logical.Response) error { if resp.Data["expiry"].(string) != "16h" { return fmt.Errorf("CRL lifetimes do not match (got %s)", resp.Data["expiry"].(string)) } return nil }, }, // Ensure that both parts of the PEM bundle are required // Here, just the cert logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "config/ca", Data: map[string]interface{}{ "pem_bundle": caCert, }, ErrorOk: true, }, // Here, just the key logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "config/ca", Data: map[string]interface{}{ "pem_bundle": caKey, }, ErrorOk: true, }, // Ensure we can fetch it back via unauthenticated means, in various formats logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "cert/ca", Unauthenticated: true, Check: func(resp *logical.Response) error { if resp.Data["certificate"].(string) != caCert { return fmt.Errorf("CA certificate:\n%s\ndoes not match original:\n%s\n", resp.Data["certificate"].(string), caCert) } return nil }, }, logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "ca/pem", Unauthenticated: true, Check: func(resp *logical.Response) error { rawBytes := resp.Data["http_raw_body"].([]byte) if string(rawBytes) != caCert { return fmt.Errorf("CA certificate:\n%s\ndoes not match original:\n%s\n", string(rawBytes), caCert) } if resp.Data["http_content_type"].(string) != "application/pkix-cert" { return fmt.Errorf("Expected application/pkix-cert as content-type, but got %s", resp.Data["http_content_type"].(string)) } return nil }, }, logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "ca", Unauthenticated: true, Check: func(resp *logical.Response) error { rawBytes := resp.Data["http_raw_body"].([]byte) pemBytes := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: rawBytes, }) if string(pemBytes) != caCert { return fmt.Errorf("CA certificate:\n%s\ndoes not match original:\n%s\n", string(pemBytes), caCert) } if resp.Data["http_content_type"].(string) != "application/pkix-cert" { return fmt.Errorf("Expected application/pkix-cert as content-type, but got %s", resp.Data["http_content_type"].(string)) } return nil }, }, // Test a bunch of generation stuff logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "root/generate/exported", Data: map[string]interface{}{ "common_name": "Root Cert", "ttl": "180h", }, Check: func(resp *logical.Response) error { intdata["root"] = resp.Data["certificate"].(string) intdata["rootkey"] = resp.Data["private_key"].(string) reqdata["pem_bundle"] = intdata["root"].(string) + "\n" + intdata["rootkey"].(string) return nil }, }, logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "intermediate/generate/exported", Data: map[string]interface{}{ "common_name": "Intermediate Cert", }, Check: func(resp *logical.Response) error { intdata["intermediatecsr"] = resp.Data["csr"].(string) intdata["intermediatekey"] = resp.Data["private_key"].(string) return nil }, }, // Re-load the root key in so we can sign it logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "config/ca", Data: reqdata, Check: func(resp *logical.Response) error { delete(reqdata, "pem_bundle") delete(reqdata, "ttl") reqdata["csr"] = intdata["intermediatecsr"].(string) reqdata["common_name"] = "Intermediate Cert" reqdata["ttl"] = "90h" return nil }, }, logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "root/sign-intermediate", Data: reqdata, Check: func(resp *logical.Response) error { delete(reqdata, "csr") delete(reqdata, "common_name") delete(reqdata, "ttl") intdata["intermediatecert"] = resp.Data["certificate"].(string) reqdata["serial_number"] = resp.Data["serial_number"].(string) reqdata["certificate"] = resp.Data["certificate"].(string) reqdata["pem_bundle"] = intdata["intermediatekey"].(string) + "\n" + resp.Data["certificate"].(string) return nil }, }, // First load in this way to populate the private key logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "config/ca", Data: reqdata, Check: func(resp *logical.Response) error { delete(reqdata, "pem_bundle") return nil }, }, // Now test setting the intermediate, signed CA cert logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "intermediate/set-signed", Data: reqdata, Check: func(resp *logical.Response) error { delete(reqdata, "certificate") return nil }, }, logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "revoke", Data: reqdata, }, logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "crl", Data: reqdata, Check: func(resp *logical.Response) error { crlBytes := resp.Data["http_raw_body"].([]byte) certList, err := x509.ParseCRL(crlBytes) if err != nil { t.Fatalf("err: %s", err) } revokedList := certList.TBSCertList.RevokedCertificates if len(revokedList) != 1 { t.Fatalf("length of revoked list not 1; %d", len(revokedList)) } revokedString := certutil.GetOctalFormatted(revokedList[0].SerialNumber.Bytes(), ":") if revokedString != reqdata["serial_number"].(string) { t.Fatalf("got serial %s, expecting %s", revokedString, reqdata["serial_number"].(string)) } delete(reqdata, "serial_number") return nil }, }, // Do it all again, with EC keys and DER format logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "root/generate/exported", Data: map[string]interface{}{ "common_name": "Root Cert", "ttl": "180h", "key_type": "ec", "key_bits": 384, "format": "der", }, Check: func(resp *logical.Response) error { certBytes, _ := base64.StdEncoding.DecodeString(resp.Data["certificate"].(string)) certPem := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: certBytes, }) keyBytes, _ := base64.StdEncoding.DecodeString(resp.Data["private_key"].(string)) keyPem := pem.EncodeToMemory(&pem.Block{ Type: "EC PRIVATE KEY", Bytes: keyBytes, }) intdata["root"] = string(certPem) intdata["rootkey"] = string(keyPem) reqdata["pem_bundle"] = string(certPem) + "\n" + string(keyPem) return nil }, }, logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "intermediate/generate/exported", Data: map[string]interface{}{ "format": "der", "key_type": "ec", "key_bits": 384, "common_name": "Intermediate Cert", }, Check: func(resp *logical.Response) error { csrBytes, _ := base64.StdEncoding.DecodeString(resp.Data["csr"].(string)) csrPem := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE REQUEST", Bytes: csrBytes, }) keyBytes, _ := base64.StdEncoding.DecodeString(resp.Data["private_key"].(string)) keyPem := pem.EncodeToMemory(&pem.Block{ Type: "EC PRIVATE KEY", Bytes: keyBytes, }) intdata["intermediatecsr"] = string(csrPem) intdata["intermediatekey"] = string(keyPem) return nil }, }, logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "config/ca", Data: reqdata, Check: func(resp *logical.Response) error { delete(reqdata, "pem_bundle") delete(reqdata, "ttl") reqdata["csr"] = intdata["intermediatecsr"].(string) reqdata["common_name"] = "Intermediate Cert" reqdata["ttl"] = "90h" return nil }, }, logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "root/sign-intermediate", Data: reqdata, Check: func(resp *logical.Response) error { delete(reqdata, "csr") delete(reqdata, "common_name") delete(reqdata, "ttl") intdata["intermediatecert"] = resp.Data["certificate"].(string) reqdata["serial_number"] = resp.Data["serial_number"].(string) reqdata["certificate"] = resp.Data["certificate"].(string) reqdata["pem_bundle"] = intdata["intermediatekey"].(string) + "\n" + resp.Data["certificate"].(string) return nil }, }, // First load in this way to populate the private key logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "config/ca", Data: reqdata, Check: func(resp *logical.Response) error { delete(reqdata, "pem_bundle") return nil }, }, // Now test setting the intermediate, signed CA cert logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "intermediate/set-signed", Data: reqdata, Check: func(resp *logical.Response) error { delete(reqdata, "certificate") return nil }, }, logicaltest.TestStep{ Operation: logical.WriteOperation, Path: "revoke", Data: reqdata, }, logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "crl", Data: reqdata, Check: func(resp *logical.Response) error { crlBytes := resp.Data["http_raw_body"].([]byte) certList, err := x509.ParseCRL(crlBytes) if err != nil { t.Fatalf("err: %s", err) } revokedList := certList.TBSCertList.RevokedCertificates if len(revokedList) != 2 { t.Fatalf("length of revoked list not 2; %d", len(revokedList)) } found := false for _, revEntry := range revokedList { revokedString := certutil.GetOctalFormatted(revEntry.SerialNumber.Bytes(), ":") if revokedString == reqdata["serial_number"].(string) { found = true } } if !found { t.Fatalf("did not find %s in CRL", reqdata["serial_number"].(string)) } delete(reqdata, "serial_number") return nil }, }, } return ret }