Repository: certbot/certbot

Issue:
Error: This security ID may not be assigned as the owner of this object
Operating system: Windows Server 2008 R2
Certbot version: 1.11.0 (installed via Certbot installer for Windows)
Web server: IIS 7.5
<hr>

I tried to obtain a certificate by running the following in the Command Prompt (with administrative rights):

```
C:\Users\Administrator>certbot certonly --webroot -w "C:\inetpub\wwwroot" -d mail.example.com
Saving debug log to C:\Certbot\log\letsencrypt.log
Plugins selected: Authenticator webroot, Installer None
Requesting a certificate for mail.example.com
Performing the following challenges:
http-01 challenge for mail.example.com
Using the webroot path C:\inetpub\wwwroot for all unmatched domains.
Cleaning up challenges
←[31mEncountered exception during recovery: FileNotFoundError: [WinError 3] The system cannot find the path specified: 'C:\\inetpub\\wwwroot\\.well-known/acme-challenge\\G_ZWXtNYqJG3gNUTTcu5aEdAZSldFeiLn57WHGgrfDg'←[0m
←[31mAn unexpected error occurred:←[0m
←[31mpywintypes.error: (1307, 'SetFileSecurity', 'This security ID may not be assigned as the owner of this object.')←[0m
Please see the logfiles in C:\Certbot\log for more details.
```

This also produced an empty `.well-known` folder in `C:\inetpub\wwwroot`.

Here we have the contents of `C:\Certbot\log\letsencrypt.log`:

```
2021-01-09 12:56:01,293:DEBUG:certbot._internal.main:certbot version: 1.11.0
2021-01-09 12:56:01,293:DEBUG:certbot._internal.main:Location of certbot entry point: C:\Program Files (x86)\Certbot\bin\certbot.exe
2021-01-09 12:56:01,293:DEBUG:certbot._internal.main:Arguments: ['--webroot', '-w', 'C:\\inetpub\\wwwroot', '-d', 'mail.example.com']
2021-01-09 12:56:01,293:DEBUG:certbot._internal.main:Discovered plugins: PluginsRegistry(PluginEntryPoint#manual,PluginEntryPoint#null,PluginEntryPoint#standalone,PluginEntryPoint#webroot)
2021-01-09 12:56:01,340:DEBUG:certbot._internal.log:Root logging level set at 20
2021-01-09 12:56:01,340:INFO:certbot._internal.log:Saving debug log to C:\Certbot\log\letsencrypt.log
2021-01-09 12:56:01,356:DEBUG:certbot._internal.plugins.selection:Requested authenticator webroot and installer None
2021-01-09 12:56:01,356:DEBUG:certbot._internal.plugins.selection:Single candidate plugin: * webroot
Description: Place files in webroot directory
Interfaces: IAuthenticator, IPlugin
Entry point: webroot = certbot._internal.plugins.webroot:Authenticator
Initialized: <certbot._internal.plugins.webroot.Authenticator object at 0x03A0F430>
Prep: True
2021-01-09 12:56:01,371:DEBUG:certbot._internal.plugins.selection:Selected authenticator <certbot._internal.plugins.webroot.Authenticator object at 0x03A0F430> and installer None
2021-01-09 12:56:01,371:INFO:certbot._internal.plugins.selection:Plugins selected: Authenticator webroot, Installer None
2021-01-09 12:56:01,371:DEBUG:certbot._internal.main:Picked account: <Account(RegistrationResource(body=Registration(key=None, contact=(), agreement=None, status=None, terms_of_service_agreed=None, only_return_existing=None, external_account_binding=None), uri='https://acme-v02.api.letsencrypt.org/acme/acct/108823696', new_authzr_uri=None, terms_of_service=None), 0e7468aa374bd9e2f4cea22ce01658fd, Meta(creation_dt=datetime.datetime(2021, 1, 8, 23, 49, 36, tzinfo=<UTC>), creation_host='WIN-12345678901', register_to_eff=None))>
2021-01-09 12:56:01,371:DEBUG:acme.client:Sending GET request to https://acme-v02.api.letsencrypt.org/directory.
2021-01-09 12:56:01,387:DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): acme-v02.api.letsencrypt.org:443
2021-01-09 12:56:02,089:DEBUG:urllib3.connectionpool:https://acme-v02.api.letsencrypt.org:443 "GET /directory HTTP/1.1" 200 658
2021-01-09 12:56:02,089:DEBUG:acme.client:Received response:
HTTP 200
Server: nginx
Date: Sat, 09 Jan 2021 10:56:04 GMT
Content-Type: application/json
Content-Length: 658
Connection: keep-alive
Cache-Control: public, max-age=0, no-cache
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800

{
  "WV94tc0c-mw": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
  "keyChange": "https://acme-v02.api.letsencrypt.org/acme/key-change",
  "meta": {
    "caaIdentities": [
      "letsencrypt.org"
    ],
    "termsOfService": "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf",
    "website": "https://letsencrypt.org"
  },
  "newAccount": "https://acme-v02.api.letsencrypt.org/acme/new-acct",
  "newNonce": "https://acme-v02.api.letsencrypt.org/acme/new-nonce",
  "newOrder": "https://acme-v02.api.letsencrypt.org/acme/new-order",
  "revokeCert": "https://acme-v02.api.letsencrypt.org/acme/revoke-cert"
}
2021-01-09 12:56:02,089:DEBUG:certbot.display.util:Notifying user: Requesting a certificate for mail.example.com
2021-01-09 12:56:02,292:DEBUG:certbot.crypto_util:Generating RSA key (2048 bits): C:\Certbot\keys\0002_key-certbot.pem
2021-01-09 12:56:02,307:DEBUG:certbot.crypto_util:Creating CSR: C:\Certbot\csr\0002_csr-certbot.pem
2021-01-09 12:56:02,323:DEBUG:acme.client:Requesting fresh nonce
2021-01-09 12:56:02,323:DEBUG:acme.client:Sending HEAD request to https://acme-v02.api.letsencrypt.org/acme/new-nonce.
2021-01-09 12:56:02,495:DEBUG:urllib3.connectionpool:https://acme-v02.api.letsencrypt.org:443 "HEAD /acme/new-nonce HTTP/1.1" 200 0
2021-01-09 12:56:02,495:DEBUG:acme.client:Received response:
HTTP 200
Server: nginx
Date: Sat, 09 Jan 2021 10:56:04 GMT
Connection: keep-alive
Cache-Control: public, max-age=0, no-cache
Link: <https://acme-v02.api.letsencrypt.org/directory>;rel="index"
Replay-Nonce: 0103YUkTf_LUykqmKEDSR9zR3Tda8BKiETA4aCDAM6RqQIU
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800


2021-01-09 12:56:02,495:DEBUG:acme.client:Storing nonce: 0103YUkTf_LUykqmKEDSR9zR3Tda8BKiETA4aCDAM6RqQIU
2021-01-09 12:56:02,495:DEBUG:acme.client:JWS payload:
b'{\n  "identifiers": [\n    {\n      "type": "dns",\n      "value": "mail.example.com"\n    }\n  ]\n}'
2021-01-09 12:56:02,510:DEBUG:acme.client:Sending POST request to https://acme-v02.api.letsencrypt.org/acme/new-order:
{
  "protected": "eyJhbGciOiAiUlMyNTYiLCAia2lkIjogImh0dHBzOi8vYWNtZS12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTA4ODIzNjk2IiwgIm5vbmNlIjogIjAxMDNZVWtUZl9MVXlrcW1LRURTUjl6UjNUZGE4QktpRVRBNGFDREFNNlJxUUlVIiwgInVybCI6ICJodHRwczovL2FjbWUtdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9uZXctb3JkZXIifQ",
  "signature": "DSl8GkfSNfhK02tO4nWkHCQTk0GShIx8oJHMIM3KoGqnoTwPFZDn2sTRLpwlf2IQNDRcUkb3hlcsg4rr3uevJVsUQiRPf0Y4ODxe98mFa6s03vO21nzsBr0QO2qOFyJu24tAMxfb7SSeEnVEwc792c32Tx4XihMaIjXq6VtaaNpIvteJNX7MssJtXbMpXUcXKmGDkX3jmnKLvQx9f_PosBhl0x7H7vr4pZiuBxEQPRUSnb0ZBLgOwKs5H2Fa3UfXim-CC30CFmMoEg3OlrhH93g7cYrwXJf9x1xU3uARhYJMOXgzYlgjKASS-YgYEdRlcVgf9R_Am3Roea1IpQd7oA",
  "payload": "ewogICJpZGVudGlmaWVycyI6IFsKICAgIHsKICAgICAgInR5cGUiOiAiZG5zIiwKICAgICAgInZhbHVlIjogIm1haWwuanVsaW5ndWEuY29tIgogICAgfQogIF0KfQ"
}
2021-01-09 12:56:02,713:DEBUG:urllib3.connectionpool:https://acme-v02.api.letsencrypt.org:443 "POST /acme/new-order HTTP/1.1" 201 338
2021-01-09 12:56:02,713:DEBUG:acme.client:Received response:
HTTP 201
Server: nginx
Date: Sat, 09 Jan 2021 10:56:05 GMT
Content-Type: application/json
Content-Length: 338
Connection: keep-alive
Boulder-Requester: 108823696
Cache-Control: public, max-age=0, no-cache
Link: <https://acme-v02.api.letsencrypt.org/directory>;rel="index"
Location: https://acme-v02.api.letsencrypt.org/acme/order/108823696/7203976924
Replay-Nonce: 0103BjmnxIkgcdMLvdBuEs2Eyb69vqiXCTLcdRx_5ZOC1MM
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800

{
  "status": "pending",
  "expires": "2021-01-15T23:50:47Z",
  "identifiers": [
    {
      "type": "dns",
      "value": "mail.example.com"
    }
  ],
  "authorizations": [
    "https://acme-v02.api.letsencrypt.org/acme/authz-v3/9929299852"
  ],
  "finalize": "https://acme-v02.api.letsencrypt.org/acme/finalize/108823696/7203976924"
}
2021-01-09 12:56:02,713:DEBUG:acme.client:Storing nonce: 0103BjmnxIkgcdMLvdBuEs2Eyb69vqiXCTLcdRx_5ZOC1MM
2021-01-09 12:56:02,713:DEBUG:acme.client:JWS payload:
b''
2021-01-09 12:56:02,729:DEBUG:acme.client:Sending POST request to https://acme-v02.api.letsencrypt.org/acme/authz-v3/9929299852:
{
  "protected": "eyJhbGciOiAiUlMyNTYiLCAia2lkIjogImh0dHBzOi8vYWNtZS12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2FjY3QvMTA4ODIzNjk2IiwgIm5vbmNlIjogIjAxMDNCam1ueElrZ2NkTUx2ZEJ1RXMyRXliNjl2cWlYQ1RMY2RSeF81Wk9DMU1NIiwgInVybCI6ICJodHRwczovL2FjbWUtdjAyLmFwaS5sZXRzZW5jcnlwdC5vcmcvYWNtZS9hdXRoei12My85OTI5Mjk5ODUyIn0",
  "signature": "MCoXoiraSUaWfs1QLaW73-b6DNOlJrFQPnzDs-SRItCNr7UwJh5dFfy0yp7J6IWhpp_pRRKIgCBBouk7sOE35snBNMdLHGJfFV83KX_E55hmff-FPq5-PPQkoggcMSdVETbcq-xCuiwTpAzpZl54SpVztXyABP9MALSCYd5ynl4vshKXl-kmnQbyDoBb57PLIiIBN89zM6Vz92IlPY1hCfCaTk4Vlw0q0iEDDjtGjI9H55unUu2GbA4ntl4dBQslzlr5_4vZP3Ww028-wZ-Y4IsEMYrIH-a7YTKpdT61eTjUuHnWCHQG5G7DCvOU7HXrOu0SZ2_VF9183jEFY77WAA",
  "payload": ""
}
2021-01-09 12:56:02,931:DEBUG:urllib3.connectionpool:https://acme-v02.api.letsencrypt.org:443 "POST /acme/authz-v3/9929299852 HTTP/1.1" 200 795
2021-01-09 12:56:02,931:DEBUG:acme.client:Received response:
HTTP 200
Server: nginx
Date: Sat, 09 Jan 2021 10:56:05 GMT
Content-Type: application/json
Content-Length: 795
Connection: keep-alive
Boulder-Requester: 108823696
Cache-Control: public, max-age=0, no-cache
Link: <https://acme-v02.api.letsencrypt.org/directory>;rel="index"
Replay-Nonce: 0103bDmFREprqZZOLr1wfG4nZPwIOPr9OsWl9u7ezjnM4Xs
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800

{
  "identifier": {
    "type": "dns",
    "value": "mail.example.com"
  },
  "status": "pending",
  "expires": "2021-01-15T23:50:47Z",
  "challenges": [
    {
      "type": "http-01",
      "status": "pending",
      "url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/9929299852/gf7IeA",
      "token": "G_ZWXtNYqJG3gNUTTcu5aEdAZSldFeiLn57WHGgrfDg"
    },
    {
      "type": "dns-01",
      "status": "pending",
      "url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/9929299852/_OSUBw",
      "token": "G_ZWXtNYqJG3gNUTTcu5aEdAZSldFeiLn57WHGgrfDg"
    },
    {
      "type": "tls-alpn-01",
      "status": "pending",
      "url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/9929299852/rh4CXA",
      "token": "G_ZWXtNYqJG3gNUTTcu5aEdAZSldFeiLn57WHGgrfDg"
    }
  ]
}
2021-01-09 12:56:02,931:DEBUG:acme.client:Storing nonce: 0103bDmFREprqZZOLr1wfG4nZPwIOPr9OsWl9u7ezjnM4Xs
2021-01-09 12:56:02,947:INFO:certbot._internal.auth_handler:Performing the following challenges:
2021-01-09 12:56:02,947:INFO:certbot._internal.auth_handler:http-01 challenge for mail.example.com
2021-01-09 12:56:02,947:INFO:certbot._internal.plugins.webroot:Using the webroot path C:\inetpub\wwwroot for all unmatched domains.
2021-01-09 12:56:02,947:DEBUG:certbot._internal.plugins.webroot:Creating root challenges validation dir at C:\inetpub\wwwroot\.well-known/acme-challenge
2021-01-09 12:56:02,947:DEBUG:certbot._internal.error_handler:Encountered exception:
Traceback (most recent call last):
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\_internal\auth_handler.py", line 70, in handle_authorizations
    resps = self.auth.perform(achalls)
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\_internal\plugins\webroot.py", line 83, in perform
    self._create_challenge_dirs()
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\_internal\plugins\webroot.py", line 185, in _create_challenge_dirs
    path, prefix, 0o755, copy_user=True, copy_group=True)
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\compat\filesystem.py", line 110, in copy_ownership_and_apply_mode
    _copy_win_ownership(src, dst)
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\compat\filesystem.py", line 622, in _copy_win_ownership
    win32security.SetFileSecurity(dst, win32security.OWNER_SECURITY_INFORMATION, security_dst)
pywintypes.error: (1307, 'SetFileSecurity', 'This security ID may not be assigned as the owner of this object.')

2021-01-09 12:56:02,947:DEBUG:certbot._internal.error_handler:Calling registered functions
2021-01-09 12:56:02,947:INFO:certbot._internal.auth_handler:Cleaning up challenges
2021-01-09 12:56:02,947:DEBUG:certbot._internal.plugins.webroot:Removing C:\inetpub\wwwroot\.well-known/acme-challenge\G_ZWXtNYqJG3gNUTTcu5aEdAZSldFeiLn57WHGgrfDg
2021-01-09 12:56:02,947:ERROR:certbot._internal.error_handler:Encountered exception during recovery: FileNotFoundError: [WinError 3] The system cannot find the path specified: 'C:\\inetpub\\wwwroot\\.well-known/acme-challenge\\G_ZWXtNYqJG3gNUTTcu5aEdAZSldFeiLn57WHGgrfDg'
2021-01-09 12:56:02,947:DEBUG:certbot._internal.log:Exiting abnormally:
Traceback (most recent call last):
  File "D:\obj\windows-release\37win32_Release\msi_python\zip_win32\runpy.py", line 193, in _run_module_as_main
  File "D:\obj\windows-release\37win32_Release\msi_python\zip_win32\runpy.py", line 85, in _run_code
  File "C:\Program Files (x86)\Certbot\bin\certbot.exe\__main__.py", line 33, in <module>
    sys.exit(main())
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\main.py", line 15, in main
    return internal_main.main(cli_args)
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\_internal\main.py", line 1421, in main
    return config.func(config, plugins)
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\_internal\main.py", line 1294, in certonly
    lineage = _get_and_save_cert(le_client, config, domains, certname, lineage)
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\_internal\main.py", line 135, in _get_and_save_cert
    lineage = le_client.obtain_and_enroll_certificate(domains, certname)
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\_internal\client.py", line 441, in obtain_and_enroll_certificate
    cert, chain, key, _ = self.obtain_certificate(domains)
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\_internal\client.py", line 374, in obtain_certificate
    orderr = self._get_order_and_authorizations(csr.data, self.config.allow_subset_of_names)
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\_internal\client.py", line 421, in _get_order_and_authorizations
    authzr = self.auth_handler.handle_authorizations(orderr, best_effort)
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\_internal\auth_handler.py", line 70, in handle_authorizations
    resps = self.auth.perform(achalls)
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\_internal\plugins\webroot.py", line 83, in perform
    self._create_challenge_dirs()
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\_internal\plugins\webroot.py", line 185, in _create_challenge_dirs
    path, prefix, 0o755, copy_user=True, copy_group=True)
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\compat\filesystem.py", line 110, in copy_ownership_and_apply_mode
    _copy_win_ownership(src, dst)
  File "C:\Program Files (x86)\Certbot\pkgs\certbot\compat\filesystem.py", line 622, in _copy_win_ownership
    win32security.SetFileSecurity(dst, win32security.OWNER_SECURITY_INFORMATION, security_dst)
pywintypes.error: (1307, 'SetFileSecurity', 'This security ID may not be assigned as the owner of this object.')
2021-01-09 12:56:02,947:ERROR:certbot._internal.log:An unexpected error occurred:
2021-01-09 12:56:02,947:ERROR:certbot._internal.log:pywintypes.error: (1307, 'SetFileSecurity', 'This security ID may not be assigned as the owner of this object.')

```




Discussion:
@adferrand, looks like webroot is setting the filepath to `'C:\\inetpub\\wwwroot\\.well-known/acme-challenge\\G_ZWXtNYqJG3gNUTTcu5aEdAZSldFeiLn57WHGgrfDg'` -- is this a known issue? It's probably caused by this line: https://github.com/certbot/certbot/blob/fb8cd063ebed6263e29dcbee2c7857d0dd069125/acme/acme/challenges.py#L336

Do we support webroot on windows?
Yes, we are supposed to support webroot. I suppose we missed the bug because there is no integration test on the webroot plugin (it would have been caught when run on Windows).

Resolving patch:
diff --git a/certbot/certbot/_internal/plugins/webroot.py b/certbot/certbot/_internal/plugins/webroot.py
--- a/certbot/certbot/_internal/plugins/webroot.py
+++ b/certbot/certbot/_internal/plugins/webroot.py
@@ -157,7 +157,8 @@ def _create_challenge_dirs(self):
                 "--webroot-path and --domains, or --webroot-map. Run with "
                 " --help webroot for examples.")
         for name, path in path_map.items():
-            self.full_roots[name] = os.path.join(path, challenges.HTTP01.URI_ROOT_PATH)
+            self.full_roots[name] = os.path.join(path, os.path.normcase(
+                challenges.HTTP01.URI_ROOT_PATH))
             logger.debug("Creating root challenges validation dir at %s",
                          self.full_roots[name])
 


Test patch:
diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py
--- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py
+++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py
@@ -148,6 +148,17 @@ def test_certonly(context):
     """Test the certonly verb on certbot."""
     context.certbot(['certonly', '--cert-name', 'newname', '-d', context.get_domain('newname')])
 
+    assert_cert_count_for_lineage(context.config_dir, 'newname', 1)
+
+
+def test_certonly_webroot(context):
+    """Test the certonly verb with webroot plugin"""
+    with misc.create_http_server(context.http_01_port) as webroot:
+        certname = context.get_domain('webroot')
+        context.certbot(['certonly', '-a', 'webroot', '--webroot-path', webroot, '-d', certname])
+
+    assert_cert_count_for_lineage(context.config_dir, certname, 1)
+
 
 def test_auth_and_install_with_csr(context):
     """Test certificate issuance and install using an existing CSR."""