Repository: minio/minio (language: go)

Issue:
AssumeRoleWithCertificate doesn't work, if minio is running under Windows
If Minio server is hosted in a Windows environment, then the [AssumeRoleWithCertificate](https://github.com/minio/minio/blob/master/docs/sts/tls.md) doesn't work.

## Expected Behavior
A MTLS webrequest to the AssumeRoleWithCertificate sts endpoint with a valid client certificate that CN is equal to an existing policy name returns valid credentials. (Works in linux hosted environment as expected!)

## Current Behavior
When the Minio server is hosted in a Windows environment, the request as described previously fails with following output:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<ErrorResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
    <Error>
        <Type></Type>
        <Code>InternalError</Code>
        <Message>Object name invalid: .minio.sys/config/iam/policydb/sts-users/tls:consoleAdmin.json</Message>
    </Error>
    <RequestId>17AD584B566D1258</RequestId>
</ErrorResponse>
```
The problem seems to be in relation to the underlying file system handling, because the `tls:` part cannot be used in file or directory names under Windows. At least the following characters are forbidden in Windows directory and file names: `\ / : * ? " < > |`. (see also: [Naming Conventions](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions))


This bug is related to #18853 (same root cause)!

## Possible Solution
Sanitized file name handling per platform, maybe as described here: https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names/61448658#61448658

## Steps to Reproduce (for bugs)

1. Host minio server under windows
2. Configure TLS handling as described in [AssumeRoleWithCertificate](https://github.com/minio/minio/blob/master/docs/sts/tls.md)
3. Make a web request with a valid client certificate as described in  https://github.com/minio/minio/blob/master/docs/sts/tls.md to the  AssumeRoleWithCertificate endpoint e.g.: `https://localhost:9111?Action=AssumeRoleWithCertificate&Version=2011-06-15&DurationSeconds=3600`

## Context
We need to host minio server in a Windows environment without a linux based sub system and our applications need a possibility to get administration credentials dynamicly for maintenance.

## Your Environment
* Version used (`minio --version`): RELEASE.2024-01-18T22-51-28Z (commit-id=19387cafab76133c2e7642de4aac8c81b9f4f8c7)  Runtime: go1.21.6 windows/amd64
* Operating System and version: Windows Server 2019/2022



Discussion / hints:

Fix patch:
diff --git a/cmd/batch-handlers.go b/cmd/batch-handlers.go
index 044e27a41a4eb..0e87cc7950bab 100644
--- a/cmd/batch-handlers.go
+++ b/cmd/batch-handlers.go
@@ -1633,7 +1633,7 @@ func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	job.ID = fmt.Sprintf("%s%s%d", shortuuid.New(), getBatchJobIDSeparator(), GetProxyEndpointLocalIndex(globalProxyEndpoints))
+	job.ID = fmt.Sprintf("%s%s%d", shortuuid.New(), getKeySeparator(), GetProxyEndpointLocalIndex(globalProxyEndpoints))
 	job.User = user
 	job.Started = time.Now()
 
diff --git a/cmd/batch-job-common-types.go b/cmd/batch-job-common-types.go
index 661bc8628709a..83e1c554b2b90 100644
--- a/cmd/batch-job-common-types.go
+++ b/cmd/batch-job-common-types.go
@@ -19,7 +19,6 @@ package cmd
 
 import (
 	"fmt"
-	"runtime"
 	"strings"
 	"time"
 
@@ -289,12 +288,3 @@ func (s *BatchJobSize) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	*s = BatchJobSize(sz)
 	return nil
 }
-
-// getBatchJobIDSeparator - returns the separator to be used in the batch job ID
-// windows requires `_` as the separator `:` will be an invalid one
-func getBatchJobIDSeparator() string {
-	if runtime.GOOS == globalWindowsOSName {
-		return "_"
-	}
-	return ":"
-}
diff --git a/cmd/bucket-listobjects-handlers.go b/cmd/bucket-listobjects-handlers.go
index 82d0fab560551..1dafdfb5ac64e 100644
--- a/cmd/bucket-listobjects-handlers.go
+++ b/cmd/bucket-listobjects-handlers.go
@@ -231,7 +231,7 @@ func parseRequestToken(token string) (subToken string, nodeIndex int) {
 	if token == "" {
 		return token, -1
 	}
-	i := strings.Index(token, getBatchJobIDSeparator())
+	i := strings.Index(token, getKeySeparator())
 	if i < 0 {
 		return token, -1
 	}
diff --git a/cmd/object-api-utils.go b/cmd/object-api-utils.go
index d58acc8878812..d095ddb6bc509 100644
--- a/cmd/object-api-utils.go
+++ b/cmd/object-api-utils.go
@@ -80,6 +80,18 @@ const (
 	compMinIndexSize = 8 << 20
 )
 
+// getkeyeparator - returns the separator to be used for
+// persisting on drive.
+//
+// - ":" is used on non-windows platforms
+// - "_" is used on windows platforms
+func getKeySeparator() string {
+	if runtime.GOOS == globalWindowsOSName {
+		return "_"
+	}
+	return ":"
+}
+
 // isMinioBucket returns true if given bucket is a MinIO internal
 // bucket and false otherwise.
 func isMinioMetaBucketName(bucket string) bool {
diff --git a/cmd/sts-handlers.go b/cmd/sts-handlers.go
index fe46f6febbe67..aac928d99a1ca 100644
--- a/cmd/sts-handlers.go
+++ b/cmd/sts-handlers.go
@@ -865,7 +865,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *h
 	}
 
 	// Associate any service accounts to the certificate CN
-	parentUser := "tls:" + certificate.Subject.CommonName
+	parentUser := "tls" + getKeySeparator() + certificate.Subject.CommonName
 
 	claims[expClaim] = UTCNow().Add(expiry).Unix()
 	claims[subClaim] = certificate.Subject.CommonName
@@ -990,7 +990,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithCustomToken(w http.ResponseWriter, r *h
 		expiry = requestedDuration
 	}
 
-	parentUser := "custom:" + res.Success.User
+	parentUser := "custom" + getKeySeparator() + res.Success.User
 
 	// metadata map
 	claims[expClaim] = UTCNow().Add(time.Duration(expiry) * time.Second).Unix()


Test patch: