Skip to content

Commit 55f4e5a

Browse files
committed
v0.1.3 update
added PBKDF2 password hashing implementation for compatibility with WebCTRL10.0 improved current location DBID detection when the Javascript SDK is invoked from within nested iframes
1 parent effe548 commit 55f4e5a

File tree

5 files changed

+120
-67
lines changed

5 files changed

+120
-67
lines changed

config/BUILD_DETAILS

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ Compilation Flags:
77
--release 11
88

99
Runtime Dependencies:
10-
addonsupport-api-addon-1.10.0
11-
alarmmanager-api-addon-1.10.0
12-
bacnet-api-core-1.11.004-20250407.1435r
13-
datatable-api-addon-1.10.0
14-
directaccess-api-addon-1.10.0
15-
logicbuilder-api-addon-1.10.0
16-
semantics-api-addon-1.10.0
17-
tomcat-embed-core-9.0.104
18-
webaccess-api-addon-1.10.0
19-
xdatabase-api-addon-1.10.0
10+
addonsupport-api-addon-1.11.0
11+
alarmmanager-api-addon-1.11.0
12+
bacnet-api-core-1.11.006-20250512.1103r
13+
datatable-api-addon-1.11.0
14+
directaccess-api-addon-1.11.0
15+
logicbuilder-api-addon-1.11.0
16+
semantics-api-addon-1.11.0
17+
tomcat-embed-core-9.0.107
18+
webaccess-api-addon-1.11.0
19+
xdatabase-api-addon-1.11.0
2020
common-9.0.002
2121
commonbaseutils-2.0.5
2222
commonexceptions-9.0.002

root/info.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<extension version="1">
22
<name>RestAPI</name>
33
<description>Exposes an OpenAPI-compatible REST API.</description>
4-
<version>0.1.2</version>
4+
<version>0.1.3</version>
55
<vendor>Automatic Controls Equipment Systems, Inc.</vendor>
66
</extension>

root/webapp/docs/api.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class WebCTRLAPIClient {
3030
this.#dbid = Number(m[1]);
3131
}else if (window.parent?.document){
3232
this.#dbid = Array.from(
33-
Array.from(window.parent.document.querySelectorAll('IFRAME') ?? []).find(
33+
Array.from(window.parent.parent.parent.parent.document.querySelectorAll('IFRAME') ?? []).find(
3434
iframe => iframe.src.includes('/navpane/')
3535
)?.contentWindow?.document?.querySelectorAll('IFRAME') ?? []
3636
).map(

root/webapp/docs/passwords.ps1

Lines changed: 107 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
# Create a function which computes a password hash compatible with WebCTRL (salted Sha512)
1+
# Create a function which computes a password hash compatible with WebCTRL (salted Sha512 or PBKDF2)
2+
# WebCTRL8.0+ is compatible with SSHA512
3+
# WebCTRL10.0+ is compatible with PBKDF2
24
function Get-PasswordHash {
35
param(
46
[Parameter(Mandatory = $true)]
5-
[string]$Password
7+
[string]$Password,
8+
[Parameter(Mandatory = $false)]
9+
[ValidateSet('SSHA512', 'PBKDF2')]
10+
[string]$HashType = 'SSHA512'
611
)
712

813
# Generate 8 bytes of random salt
@@ -11,29 +16,48 @@ function Get-PasswordHash {
1116
$rng.GetBytes($Salt)
1217
$rng.Dispose()
1318

14-
# Convert password to UTF-8 bytes
15-
$passwordBytes = [System.Text.Encoding]::UTF8.GetBytes($Password)
16-
17-
# Concatenate password and salt
18-
$data = New-Object byte[] ($passwordBytes.Length + $Salt.Length)
19-
[System.Buffer]::BlockCopy($passwordBytes, 0, $data, 0, $passwordBytes.Length)
20-
[System.Buffer]::BlockCopy($Salt, 0, $data, $passwordBytes.Length, $Salt.Length)
21-
22-
# Compute SHA-512 hash
23-
$sha512 = [System.Security.Cryptography.SHA512]::Create()
24-
$hashBytes = $sha512.ComputeHash($data)
25-
$sha512.Dispose()
26-
27-
# Concatenate hash and salt
28-
$result = New-Object byte[] ($hashBytes.Length + $Salt.Length)
29-
[System.Buffer]::BlockCopy($hashBytes, 0, $result, 0, $hashBytes.Length)
30-
[System.Buffer]::BlockCopy($Salt, 0, $result, $hashBytes.Length, $Salt.Length)
31-
32-
# Convert to base64
33-
$base64 = [System.Convert]::ToBase64String($result)
34-
35-
# Return with {SSHA512} prefix
36-
return '{SSHA512}' + $base64
19+
if ($HashType -eq 'PBKDF2') {
20+
# PBKDF2WithHmacSHA512: 210000 iterations, 512-bit hash
21+
$pbkdf2 = New-Object System.Security.Cryptography.Rfc2898DeriveBytes($Password, $Salt, 210000, [System.Security.Cryptography.HashAlgorithmName]::SHA512)
22+
$hashBytes = $pbkdf2.GetBytes(64) # 512 bits = 64 bytes
23+
$pbkdf2.Dispose()
24+
25+
# Concatenate hash and salt
26+
$result = New-Object byte[] ($hashBytes.Length + $Salt.Length)
27+
[System.Buffer]::BlockCopy($hashBytes, 0, $result, 0, $hashBytes.Length)
28+
[System.Buffer]::BlockCopy($Salt, 0, $result, $hashBytes.Length, $Salt.Length)
29+
30+
# Convert to base64
31+
$base64 = [System.Convert]::ToBase64String($result)
32+
33+
# Return with {PBKDF2} prefix
34+
return '{PBKDF2}' + $base64
35+
}
36+
else {
37+
# Convert password to UTF-8 bytes
38+
$passwordBytes = [System.Text.Encoding]::UTF8.GetBytes($Password)
39+
40+
# Concatenate password and salt
41+
$data = New-Object byte[] ($passwordBytes.Length + $Salt.Length)
42+
[System.Buffer]::BlockCopy($passwordBytes, 0, $data, 0, $passwordBytes.Length)
43+
[System.Buffer]::BlockCopy($Salt, 0, $data, $passwordBytes.Length, $Salt.Length)
44+
45+
# Compute SHA-512 hash
46+
$sha512 = [System.Security.Cryptography.SHA512]::Create()
47+
$hashBytes = $sha512.ComputeHash($data)
48+
$sha512.Dispose()
49+
50+
# Concatenate hash and salt
51+
$result = New-Object byte[] ($hashBytes.Length + $Salt.Length)
52+
[System.Buffer]::BlockCopy($hashBytes, 0, $result, 0, $hashBytes.Length)
53+
[System.Buffer]::BlockCopy($Salt, 0, $result, $hashBytes.Length, $Salt.Length)
54+
55+
# Convert to base64
56+
$base64 = [System.Convert]::ToBase64String($result)
57+
58+
# Return with {SSHA512} prefix
59+
return '{SSHA512}' + $base64
60+
}
3761
}
3862

3963
# Create a function to validate a given password against the stored hash value
@@ -45,35 +69,64 @@ function Confirm-PasswordHash {
4569
[string]$Hash
4670
)
4771

48-
# Remove the {SSHA512} prefix and decode base64
49-
$base64Data = $Hash.Substring(9) # Remove '{SSHA512}' prefix (9 characters)
50-
$data = [System.Convert]::FromBase64String($base64Data)
51-
52-
# Extract salt (last 8 bytes) and hash (first len-8 bytes)
53-
$len = $data.Length
54-
$salt = New-Object byte[] 8
55-
$storedHash = New-Object byte[] ($len - 8)
56-
57-
# Use BlockCopy to extract salt (last 8 bytes)
58-
[System.Buffer]::BlockCopy($data, $len - 8, $salt, 0, 8)
59-
# Use BlockCopy to extract hash (first len-8 bytes)
60-
[System.Buffer]::BlockCopy($data, 0, $storedHash, 0, $len - 8)
61-
62-
# Convert password to UTF-8 bytes
63-
$passwordBytes = [System.Text.Encoding]::UTF8.GetBytes($Password)
64-
65-
# Concatenate password and salt
66-
$dataToHash = New-Object byte[] ($passwordBytes.Length + $salt.Length)
67-
[System.Buffer]::BlockCopy($passwordBytes, 0, $dataToHash, 0, $passwordBytes.Length)
68-
[System.Buffer]::BlockCopy($salt, 0, $dataToHash, $passwordBytes.Length, $salt.Length)
69-
70-
# Compute SHA-512 hash
71-
$sha512 = [System.Security.Cryptography.SHA512]::Create()
72-
$computedHash = $sha512.ComputeHash($dataToHash)
73-
$sha512.Dispose()
74-
75-
# Compare the computed hash with the stored hash
76-
return Compare-ByteArrays $computedHash $storedHash
72+
# Detect hash type based on prefix
73+
if ($Hash.StartsWith('{PBKDF2}')) {
74+
# Remove the {PBKDF2} prefix and decode base64
75+
$base64Data = $Hash.Substring(8) # Remove '{PBKDF2}' prefix (8 characters)
76+
$data = [System.Convert]::FromBase64String($base64Data)
77+
78+
# Extract salt (last 8 bytes) and hash (first len-8 bytes)
79+
$len = $data.Length
80+
$salt = New-Object byte[] 8
81+
$storedHash = New-Object byte[] ($len - 8)
82+
83+
# Use BlockCopy to extract salt (last 8 bytes)
84+
[System.Buffer]::BlockCopy($data, $len - 8, $salt, 0, 8)
85+
# Use BlockCopy to extract hash (first len-8 bytes)
86+
[System.Buffer]::BlockCopy($data, 0, $storedHash, 0, $len - 8)
87+
88+
# Compute PBKDF2 hash with same parameters
89+
$pbkdf2 = New-Object System.Security.Cryptography.Rfc2898DeriveBytes($Password, $salt, 210000, [System.Security.Cryptography.HashAlgorithmName]::SHA512)
90+
$computedHash = $pbkdf2.GetBytes(64) # 512 bits = 64 bytes
91+
$pbkdf2.Dispose()
92+
93+
# Compare the computed hash with the stored hash
94+
return Compare-ByteArrays $computedHash $storedHash
95+
}
96+
elseif ($Hash.StartsWith('{SSHA512}')) {
97+
# Remove the {SSHA512} prefix and decode base64
98+
$base64Data = $Hash.Substring(9) # Remove '{SSHA512}' prefix (9 characters)
99+
$data = [System.Convert]::FromBase64String($base64Data)
100+
101+
# Extract salt (last 8 bytes) and hash (first len-8 bytes)
102+
$len = $data.Length
103+
$salt = New-Object byte[] 8
104+
$storedHash = New-Object byte[] ($len - 8)
105+
106+
# Use BlockCopy to extract salt (last 8 bytes)
107+
[System.Buffer]::BlockCopy($data, $len - 8, $salt, 0, 8)
108+
# Use BlockCopy to extract hash (first len-8 bytes)
109+
[System.Buffer]::BlockCopy($data, 0, $storedHash, 0, $len - 8)
110+
111+
# Convert password to UTF-8 bytes
112+
$passwordBytes = [System.Text.Encoding]::UTF8.GetBytes($Password)
113+
114+
# Concatenate password and salt
115+
$dataToHash = New-Object byte[] ($passwordBytes.Length + $salt.Length)
116+
[System.Buffer]::BlockCopy($passwordBytes, 0, $dataToHash, 0, $passwordBytes.Length)
117+
[System.Buffer]::BlockCopy($salt, 0, $dataToHash, $passwordBytes.Length, $salt.Length)
118+
119+
# Compute SHA-512 hash
120+
$sha512 = [System.Security.Cryptography.SHA512]::Create()
121+
$computedHash = $sha512.ComputeHash($dataToHash)
122+
$sha512.Dispose()
123+
124+
# Compare the computed hash with the stored hash
125+
return Compare-ByteArrays $computedHash $storedHash
126+
}
127+
else {
128+
throw "Unsupported hash format. Expected {SSHA512} or {PBKDF2} prefix."
129+
}
77130
}
78131

79132
# Helper function to compare two byte arrays

src/aces/webctrl/restapi/api/exec/CreateOperator.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"hashed": {
2424
"type": "boolean",
2525
"default": false,
26-
"description": "Set to true if the password is already hashed. Note that hashed passwords must be prefixed with '{SSHA512}'."
26+
"description": "Set to true if the password is already hashed. Note that hashed passwords must be prefixed with '{SSHA512}' (WebCTRL8.0+) or '{PBKDF2}' (WebCTRL10.0+)."
2727
},
2828
"temporary": {
2929
"type": "boolean",

0 commit comments

Comments
 (0)