Open timothy-byles opened 1 year ago
Thank you for your suggestions!
So, these code snippets are copied from your LDAP project or do you happen to have a modified version of the ActivateSignatures.ps1 with these improvements? In the latter case, it would be nice if you could open two Pull Requests for these suggestions, which would help put these additions into the right place in ActivateSignatures.ps1.
And since you mention the LDAP address book project ... I did that once, too, a long time ago, and wrote a tool in C++ as part of my thesis at the university. In my thesis, I described the registry settings for LDAP address books in Outlook (pages 83-87). I am attaching it here, maybe you get something out of it for your LDAP code. It is in German though.
Diplomarbeit_Informatik_Final.pdf
Is your LDAP address book tool available to the public somewhere? Some of my colleagues are interested in such a tool if it was a nice PowerShell script instead of decade-old C++.
The code I provided are snippets from my project. I can't share it in full as-is because it has references to private servers. Maybe I can remove those and provide a more generic version? It's based off of this project written in vbscript https://www.codeproject.com/Articles/14053/Adding-an-LDAP-address-book-to-MS-Outlook
I did not plan to use ActivateSignatures as-is because I need to make some significant modifications and some of these may be specific to our environment. We have over 100k users. Some of them may have multiple smartcards. Some users have other users insert their smartcards which means one user will have many other unused certificates in their cert store. I intend to do these actions
So here is some more information on the binary blob for ActivateSignatures.ps1 0x00 uint32 - Number of entries. First entry offset will always begin at offset 4 + (number entries * 0x10)
Next section is number of entries * 0x10 0x00 uint64 - size of this entry 0x08 uint64 - offset of this entry
Then comes each entry 0x00 16 bytes Looks to be a static GUID. Same for each entry 0x10 uint32 - Enum structure for options 0x14 begininng of settings which you have discovered
enum for options 1 and 2 are the "Default Security Setting..." options. Can't tell which is which because I'm unable to check one without the other 4 is the option to "Send these certificates with signed messages"
So (at least for my purposes), Default option will be 7, others will be 4
This information is important to me because I need to be able to parse multiple entries and check each one. Users may purposely have configured multiple settings but my app will remove any entries which point to certificates which are no longer available
If any of this information is not clear, just let me know and I will try to explain better
I don't fully understand the significance of this but my algorithms section is different from yours. Mine is 0x89 bytes long and looks to include 10 elements. For reference, I am using Office 365 C2R
30 81 82 30 0b 06 09 60 86 48 01 65 03 04 01 2a 30 0b 06 09 60 86 48 01 65 03 04 01 16 30 0a 06 08 2a 86 48 86 f7 0d 03 07 30 0b 06 09 60 86 48 01 65 03 04 01 02 30 0e 06 08 2a 86 48 86 f7 0d 03 02 02 02 00 80 30 0d 06 08 2a 86 48 86 f7 0d 03 02 02 01 40 30 07 06 05 2b 0e 03 02 1a 30 0b 06 09 60 86 48 01 65 03 04 02 03 30 0b 06 09 60 86 48 01 65 03 04 02 02 30 0b 06 09 60 86 48 01 65 03 04 02 01
This is your algorithms ASN.1-decoded:
<30 81 82>
000 82: SEQUENCE {
<30 0B>
003 B: SEQUENCE {
<06 09>
005 9: OBJECT IDENTIFIER aes256-CBC (2 16 840 1 101 3 4 1 42)
: (NIST Algorithm)
: }
<30 0B>
010 B: SEQUENCE {
<06 09>
012 9: OBJECT IDENTIFIER aes192-CBC (2 16 840 1 101 3 4 1 22)
: (NIST Algorithm)
: }
<30 0A>
01D A: SEQUENCE {
<06 08>
01F 8: OBJECT IDENTIFIER des-EDE3-CBC (1 2 840 113549 3 7)
: (RSADSI encryptionAlgorithm)
: }
<30 0B>
029 B: SEQUENCE {
<06 09>
02B 9: OBJECT IDENTIFIER aes128-CBC (2 16 840 1 101 3 4 1 2)
: (NIST Algorithm)
: }
<30 0E>
036 E: SEQUENCE {
<06 08>
038 8: OBJECT IDENTIFIER rc2CBC (1 2 840 113549 3 2)
: (RSADSI encryptionAlgorithm)
<02 02>
042 2: INTEGER 128
: }
<30 0D>
046 D: SEQUENCE {
<06 08>
048 8: OBJECT IDENTIFIER rc2CBC (1 2 840 113549 3 2)
: (RSADSI encryptionAlgorithm)
<02 01>
052 1: INTEGER 64
: }
<30 07>
055 7: SEQUENCE {
<06 05>
057 5: OBJECT IDENTIFIER sha1 (1 3 14 3 2 26)
: (OIW)
: }
<30 0B>
05E B: SEQUENCE {
<06 09>
060 9: OBJECT IDENTIFIER sha-512 (2 16 840 1 101 3 4 2 3)
: (NIST Algorithm)
: }
<30 0B>
06B B: SEQUENCE {
<06 09>
06D 9: OBJECT IDENTIFIER sha-384 (2 16 840 1 101 3 4 2 2)
: (NIST Algorithm)
: }
<30 0B>
078 B: SEQUENCE {
<06 09>
07A 9: OBJECT IDENTIFIER sha-256 (2 16 840 1 101 3 4 2 1)
: (NIST Algorithm)
: }
: }
This is what ActivateSignatures writes:
<30 5A>
000 5A: SEQUENCE {
<30 0B>
002 B: SEQUENCE {
<06 09>
004 9: OBJECT IDENTIFIER aes256-CBC (2 16 840 1 101 3 4 1 42)
: (NIST Algorithm)
: }
<30 0B>
00F B: SEQUENCE {
<06 09>
011 9: OBJECT IDENTIFIER aes192-CBC (2 16 840 1 101 3 4 1 22)
: (NIST Algorithm)
: }
<30 0A>
01C A: SEQUENCE {
<06 08>
01E 8: OBJECT IDENTIFIER des-EDE3-CBC (1 2 840 113549 3 7)
: (RSADSI encryptionAlgorithm)
: }
<30 0B>
028 B: SEQUENCE {
<06 09>
02A 9: OBJECT IDENTIFIER aes128-CBC (2 16 840 1 101 3 4 1 2)
: (NIST Algorithm)
: }
<30 0B>
035 B: SEQUENCE {
<06 09>
037 9: OBJECT IDENTIFIER sha-256 (2 16 840 1 101 3 4 2 1)
: (NIST Algorithm)
: }
<30 0B>
042 B: SEQUENCE {
<06 09>
044 9: OBJECT IDENTIFIER sha-512 (2 16 840 1 101 3 4 2 3)
: (NIST Algorithm)
: }
<30 0B>
04F B: SEQUENCE {
<06 09>
051 9: OBJECT IDENTIFIER sha-384 (2 16 840 1 101 3 4 2 2)
: (NIST Algorithm)
: }
: }
This change was a result of #1. I removed some older algorithms to prevent downgrade attacks.
Interesting finding about multiple entries! I haven't tested it, but your analysis that the first number is the number of entries (my code assumes it is always 1) is plausible and also the offset thing that I assumed to be the length of the header (and it is if there is just one entry).
The actual entry seems to consist a list of properties. Each property (I call them "packets" in the code) has a two-byte type and then a two-byte length. The length value includes the four bytes of type and length and then the remaining data. What you found to be a static Guid are two properties, that I also found to be the same all the time. The first is of type 1 and value 1 and the second is type 6 and value 1, whatever that means. And then what you found to be the enum structure for options is type 0x20 and I always write value 7. You says that this is the default, but other values are also possible.
And then come the name of the setting (type 0x51 as Unicode and 0x0b as ASCII), the algorithms used (type 0x02), the hash of the signing certificate (type 0x09) and the hash of the encryption certificate (type 0x22).
If there are multiple entries/Security Settings, I wonder how Outlook knows which one to use. I would have suspected that this is what the properties of 1 and 6 might be for, but you said they were always the same so this speaks strongly against it. This requires further analysis.
I just started with code to parse multiple entries in the pull request above.
I'm working on a custom class for the whole blob as well as for each settings entry. It's not quite ready yet but here's what I have so far
Set-Variable ESConfigGuid -Option ReadOnly -value ([guid]'00080001-0001-0000-2000-080001000000')
Set-Variable AlgsAsn1 -Option ReadOnly -value ([byte[]]@(0x30,0x81,0x82,0x30,0x0b,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x01,0x2a,0x30,0x0b,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x01,0x16,0x30,0x0a,0x06,0x08,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x03,0x07,0x30,0x0b,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x01,0x02,0x30,0x0e,0x06,0x08,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x03,0x02,0x02,0x02,0x00,0x80,0x30,0x0d,0x06,0x08,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x03,0x02,0x02,0x01,0x40,0x30,0x07,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x30,0x0b,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x03,0x30,0x0b,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x02,0x30,0x0b,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x01))
[Flags()] enum ESConfigOption {
Default1 = 1
Default2 = 2
SendWithMsgs = 4
}
enum ESConfigItemID {
AsnHashList = 0x02
SignatureCertHash = 0x09
NameA = 0x0B
EncryptionCertHash = 0x22
NameW = 0x51
}
class ESConfigEntry {
[string]$Name = ''
[ESConfigOption]$Options = 4
[string]$EncryptionCertThumbprint
[string]$SignatureCertThumbprint
[byte[]]$HashAlgorithms = $AlgsAsn1
[byte[]]$EncryptionCertHash
[byte[]]$SignatureCertHash
[byte[]]$RawData
ESConfigEntry() {
$this.PSObject.Properties.Add([PSScriptProperty]::New('Length',{$this.uLength},{}))
}
ESConfigEntry([byte[]]$blob) {
$this.RawData = $blob
$guid = [guid]::new($blob[0..15])
if (! $ESConfigGuid -ne $guid) {throw "Unexpected Guid - $guid"}
$pos = 0x14
$this.Options = [bitconverter]::ToUint32($blob, 16)
while ($pos -lt $blob.length) {
$Id = [bitconverter]::ToUint16($blob, $pos)
$itemSize = [bitconverter]::ToUint16($blob, $pos + 4)
$end = $pos + $itemSize - 1
switch ($Id) {
[ESConfigItemID]::NameW {
if ($this.Name.Length -eq 0) {$this.Name =
[Text.Encoding]::Unicode.GetString($blob[($pos + 8)..$end])}
}
[ESConfigItemID]::NameA {
if ($this.Name.Length -eq 0) {$this.Name =
[Text.Encoding]::ASCII.GetString($blob[$pos + 8)..$end])}
}
}
[ESConfigItemID]::EncryptionCertHash {
if ($itemSize -ne 0x14) {throw "Invalid hash size: $itemSize, expected 20"}
$data = $blob[($pos + 8)..$end]
$this.EncryptionCertThumbprint = ([bitconverter]::ToString($data)).Replace('-','')
$this.EncryptionCertHash = $data
}
[ESConfigItemID]::SignatureCertHash {
if ($itemSize -ne 0x14) {throw "Invalid hash size: $itemSize, expected 20"}
$data = $blob[($pos + 8)..$end]
$this.SignatureCertThumbprint = ([bitconverter]::ToString($data)).Replace('-','')
$this.SignatureCertHash = $data
}
[ESConfigItemID]::AsnHashList
# Maybe I can decode the Asn data later if need arises
# For now we'll just use what I found in my Outlook M365
$this.HashAlgorithms = $blob[($pos + 8)..$end]
}
$pos = $end + 1
}
}
[byte[]]GetBytes() {
#Calculate Total size and create a byte array of that size
#Since we need the name in ASCII as well as Unicode and both null-terminated...
$size = ($this.Name.Length + 1) * 3
$size +=
}
}
class ESConfig {
[ESConfigEntry[]]$Entries
ESConfig( [byte[]]$ba ) {
# Do some basic error checking
$pos = 0
$numEntries = [bitconverter]::ToUint32($ba, 0)
if ($ba.Length -lt (4 + ($numEntries * 0x10))) {throw 'Unable to parse data'}
$pos = (($numEntries - 1) * 0x10) + 4
if ([bitconverter]::ToUint64($ba, $pos) + [bitconverter]::ToUint32($ba, $pos + 8) -ne $ba.Length) {
throw 'Unable to parse data'}
$this.Entries = [ESConfigEntry[]]::new($numEntries)
for ($i = 0;$i -lt $numEntries;$i++) {
$pos = 4 + ($i * 0x10)
$entrySize = [bitconverter]::ToUint64($ba, $pos)
$offset = [bitconverter]::ToUint64($ba, $pos + 8)
[byte[]]$blob = $ba[($offset)..($offset + $entrySize - 1)]
$this.Entries[$i] = [ESConfigEntry]::new($blob)
}
}
}
When the class is complete you'll use it like this
$RegEntry = (Get-ItemProperty 'HKCU:\SOFTWARE\Microsoft\Office\16.0\Outlook\Profiles\Outlook\c02ebc5353d9cd11975200aa004ae40e' -Name '11020355').11020355 $Settings = [ESConfig]::New($RegEntry)
This object will hold an array of the settings entries $Settings.Entries
Add/remove or modify properties of individual settings and then when done you get the whole binary blob back like this [byte[]]$NewRegEntry = $Settings.GetBytes()
Sounds good. So I suggest you write that class and I review it and include it in ActivateSignatures.ps1. Then we don't have duplicate work and good quality with a four-eye-principle.
Regarding the GetBytes ... I think you don't need to know the length at that point. It's the ESConfig::GetBytes that needs to know the length, not the ESConfigEntry::GetBytes.
I think you're referring to this line?
$this.PSObject.Properties.Add([PSScriptProperty]::New('Length',{$this.uLength},{})
This dynamically adds a readonly property to the object instance. It allows you to get the length of the binary blob and doesn't allow to write to the property.
Interesting, didn't know that about dynamically added properties and I missed that line when I looked onto the code. So actually, I was referring to this other part:
[byte[]]GetBytes() {
#Calculate Total size and create a byte array of that size
#Since we need the name in ASCII as well as Unicode and both null-terminated...
$size = ($this.Name.Length + 1) * 3
$size +=
}
Oh ok. You are correct, that belongs in the GetBytes() method of ESConfig instead. TY
Actually, I remember now that I need that there so that I can create the correct sized Array. This will be more efficient than generating separate arrays and trying to concatenate them
Just wanted to give a progress update. Decided to make a custom class for the Algorithm blob. You can now use flags to set the 'chosen' algorith for each as well as what others are available in the dropdown.
I'm currently working diligently to finish the class for settings entry and then the settings collection should be trivial and quick compared to the first two.
Here is the Algorithm class. I may neeed to clean up a few uneccessary bits that I used during testing
# Helper functions
# Unless I missed something, PS doesn't provide methods to find index of a sub-array
# This works just like [string].SubString
function FindInArray([byte[]]$source, [byte[]]$find) {
if ($find.Length -gt $source.Length) { throw 'search bytes are longer than source' }
$end = $source.Length - $find.Length
$iFound = -1
for ($pos = 0; ($iFound -eq -1) -and ($pos -le ($end)); $pos++) {
if ($source[$pos] -eq $find[0]) {
$iFound = $pos
for ($pos2 = 1; $pos2 -lt $find.Length; $pos2++) {
if ($source[$pos + $pos2] -ne $find[$pos2]) {$iFound = -1; break}
}
}
}
return $iFound
}
# Very efficient and clever function I found on Stackoverflow, but adapted to PS
function CountFlags([enum]$flags) {
$count = 0
while ($flags) {
$flags = $flags -band ($flags - 1)
$count++
}
return $count
}
########################### BEGIN ESAlgorithmAsn custom class ###########################
[Flags()] enum CryptAlgs {
AES_256 = 1
AES_192 = 2
TripleDES = 4
AES_128 = 8
RC2_128 = 16
RC2_64 = 32
}
# I could not figure out how to assign these during initialization so we'll just do them one-by-one >:-[
# Hashtable for ASN1 encoded encryption algorithms
$CryptAsn = @{}
$CryptAsn[[CryptAlgs]::AES_256] = [byte[]]@(0x30, 0x0B, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2A)
$CryptAsn[[CryptAlgs]::AES_192] = [byte[]]@(0x30, 0x0B, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x16)
$CryptAsn[[CryptAlgs]::TripleDES] = [byte[]]@(0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x03, 0x07)
$CryptAsn[[CryptAlgs]::AES_128] = [byte[]]@(0x30, 0x0B, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x02)
$CryptAsn[[CryptAlgs]::RC2_128] = [byte[]]@(0x30, 0x0E, 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x03, 0x02, 0x02, 0x02, 0x00, 0x80)
$CryptAsn[[CryptAlgs]::RC2_64] = [byte[]]@(0x30, 0x0D, 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x03, 0x02, 0x02, 0x01, 0x40)
[Flags()] enum HashAlgs {
SHA1 = 1
SHA_512 = 2
SHA_384 = 4
SHA_256 = 8
}
# Hashtable for ASN1 encoded hash algorithms
$HashAsn = @{}
$HashAsn[[HashAlgs]::SHA1] = [byte[]]@(0x30, 0x07, 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A)
$HashAsn[[HashAlgs]::SHA_512] = [byte[]]@(0x30, 0x0B, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03)
$HashAsn[[HashAlgs]::SHA_384] = [byte[]]@(0x30, 0x0B, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02)
$HashAsn[[HashAlgs]::SHA_256] = [byte[]]@(0x30, 0x0B, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01)
# Custom class to handle creating or modifying the ASN1 data that Outlook holds in it's email security settings
class ESAlgorithmAsn {
# It's just one contiguous stream but the 'chosen' algs are moved to the head of the stream
# All encryption entries are listed first then hash entries
# This is strictly ASN1 encoded data. The accompanying header is handled by the ESConfigEntry class
[CryptAlgs]$EncryptionAlgorithm
[CryptAlgs]$OtherEncryptionAlgorithms
[HashAlgs]$HashAlgorithm
[HashAlgs]$OtherHashAlgorithms
# Create a new instance with only one of each default and set them to the strongest available
# This could be moved to a settings section at the beginning of the script
ESAlgorithmAsn() {
$this.EncryptionAlgorithm = [CryptAlgs]::AES_256
$this.HashAlgorithm = [HashAlgs]::SHA_512
}
# Is the appropriate way to handle this? Should it be a function like LoadData($stream) ?
# Maybe we could define a class function and here we would just use that function?
ESAlgorithmAsn([byte[]]$stream) {
$CryptAsn = $Global:CryptAsn
$HashAsn = $Global:HashAsn
# Currently we use FindInArray to search for each blob in the stream
# I wonder if it would be more efficient to step through the stream?
# Need to see what that code looks like since doing it this way loops through the stream quite a few times
# However, this is very fast as-is and stepping through the stream means needing to do some basic parsing
# firstEnum holds the enum of the blob found with the lowest index
# This becomes the default while others are added to the $this.Otherxxx
$firstEnum = 0
$firstPos = -1
foreach ($enum in [CryptAlgs].GetEnumNames()) {
$pos = FindInArray $stream $CryptAsn[[CryptAlgs]::$enum]
if ($pos -ne -1) {
$this.OtherEncryptionAlgorithms += [CryptAlgs]::$enum
if (($firstPos -eq -1) -or ($pos -lt $firstPos)) {
$firstPos = $pos
$firstEnum = [CryptAlgs]::$enum
}
}
}
$this.EncryptionAlgorithm = $firstEnum
# We know that Other also includes the default so we can subtract instead of using -band -bnot (see in GetBytes())
$this.OtherEncryptionAlgorithms -= $firstEnum
# Forgot to reset these at first and of course, got some unexpected results
$firstEnum = 0
$firstPos = -1
foreach ($enum in [HashAlgs].GetEnumNames()) {
$pos = FindInArray $stream $HashAsn[[HashAlgs]::$enum]
if ($pos -ne -1) {
$this.OtherHashAlgorithms += [HashAlgs]::$enum
if (($firstPos -eq -1) -or ($pos -lt $firstPos)) {
$firstPos = $pos
$firstEnum = [HashAlgs]::$enum
}
}
}
$this.HashAlgorithm = $firstEnum
$this.OtherHashAlgorithms -= $firstEnum
}# End constructor ESAlgorithmAsn($stream)
# Build and return the stream
[byte[]]GetBytes() {
if ($this.HashAlgorithm -eq 0) {throw 'Must include default hash algorithm'}
if ($this.EncryptionAlgorithm -eq 0) {throw 'Must include default encryption algorithm'}
if ((CountFlags $this.HashAlgorithm) -ne 1) {throw "Invalid flags for default Hash Algorithm: $this.HashAlgorithm"}
if ((CountFlags $this.EncryptionAlgorithm) -ne 1) {throw "Invalid flags for default Encryption Algorithm: $this.EncryptionAlgorithm"}
# Make sure that Other doesn't contain the default flag
$this.OtherEncryptionAlgorithms = $this.OtherEncryptionAlgorithms -band (-bnot $this.EncryptionAlgorithm)
$this.OtherHashAlgorithms = $this.OtherHashAlgorithms -band (-bnot $this.HashAlgorithm)
# Globals aren't available here... who knew...
$CryptAsn = $Global:CryptAsn
$HashAsn = $Global:HashAsn
[uint32]$size = 0
$outputArray = $null
$pos = 0
$allCrypts = $this.EncryptionAlgorithm -bor $this.OtherEncryptionAlgorithms
$allHashes = $this.HashAlgorithm -bor $this.OtherHashAlgorithms
$defaultCrypt = $null
# Loop through enum values and see if each flag exists
foreach ($enum in [CryptAlgs].GetEnumNames()) {
if ($allCrypts -band [CryptAlgs]::$enum) {$size += $CryptAsn[[CryptAlgs]::$enum].length}
}
foreach ($enum in [HashAlgs].GetEnumNames()) {
if ($allHashes -band [HashAlgs]::$enum){$size += $HashAsn[[HashAlgs]::$enum].length}
}
# I can't imagine this string being longer than 65535 but we're going to code for it anyway
$sizeBytes = [bitconverter]::GetBytes($size)
$pos = 2
# I thought about using -band 0x80 here but if, for example, the size is 0x100, if would fail the check
if ($size -lt 0x80) {
$outputArray = [byte[]]::new($size + $pos)
$outputArray[0] = [byte]0x30
$outputArray[1] = [byte]$size
} else {
#When total length is greater than 0x80, we have to set the greatest bit and use the
# rest of this byte to define how many additional bytes are needed to express the total size
# For example: if total size is 0xFF00FF then the ASN1 header will look like this
# 0x0 = 0x30
# 0x1 = 0x83 <- 0x80 || 0x03 for 3 bytes to express FF00FF
# 0x30 0x83 0xFF 0x00 0xFF <rest of ASN1 data>
# Find the last non-zero byte
# If this is running on a Big-Endian platform, this needs to be re-worked to detect endianness
$iSize = 0
foreach ($i in ($sizeBytes.Length - 1)..0) {if ($sizeBytes[$i]) {$iSize = $i; break}}
$outputArray = [byte[]]::new($size + $pos + $iSize + 1)
$outputArray[0] = [byte]0x30
$outputArray[1] = [byte](0x80 + $iSize + 1)
# ASN1 stores numbers in Big-Endian so we need to reverse the byte order
foreach ($i in $iSize..0) {
$outputArray[$pos++] = $sizeBytes[$i] #Set the byte at $pos and then move to the next byte
}
}
# Header is complete, now we can compile the ASN1 data
$defaultCrypt = $CryptAsn[$this.EncryptionAlgorithm]
$defaultCrypt.CopyTo($outputArray, $pos)
$pos += $defaultCrypt.Length
if ($this.OtherEncryptionAlgorithms) {
foreach ($enum in [CryptAlgs].GetEnumNames()) {
if ($this.OtherEncryptionAlgorithms -band [CryptAlgs]::$enum) {
$addBytes = $CryptAsn[[CryptAlgs]::$enum]
$addBytes.CopyTo($outputArray, $pos)
$pos += $addBytes.Length
}
}
}
$defaultHash = $HashAsn[$this.HashAlgorithm]
$defaultHash.CopyTo($outputArray, $pos)
$pos += $defaultHash.Length
if ($this.OtherHashAlgorithms) {
foreach ($enum in [HashAlgs].GetEnumNames()) {
if ($this.OtherHashAlgorithms -band [HashAlgs]::$enum) {
$addBytes = $HashAsn[[HashAlgs]::$enum]
$addBytes.CopyTo($outputArray, $pos)
$pos += $addBytes.Length
}
}
}
return $outputArray
}# End method GetBytes
}# End class ESAlgorithmAsn
########################### END ESAlgorithmAsn custom class ###########################
Updated previous comment. Fixed a few bugs and cleaned up as well as added more comments. It's currently on CodeReview stack exchange. Hopefully I can finish the other classes in the next day or two
Excellent, if I have any comments on this part of the code, I will post it on Code Review.
I have finished the project. I am currently reviewing my own code and adding comments. I will also document usage and then update the code review page as well as upload to my github repo
I finally got this comlete, commented, and documented
https://github.com/timothy-byles/powershell-OutlookES-classes
Next I'll see if I can strip the sensitive info from the Address book script and upload that to another repo
Looks very good, I will see to it that I add this as a dependency here and use it directly.
RE: the address book script. I found a project that does this via MAPI https://github.com/andreighita/MAPIToolkit I think my script is very specific and it has binary data that I haven't been able to decode so I don't know if it includes sensitive data
The best I could do is pull out all the data, and whoever uses the script would need to manually create an address book to be able to get the data for those sections and insert them into the script. I remember one registry value had data that seemed to be different each time I crated a new entry. I think that the above linked MAPI project should work better and with some time and effort, Powershell could perform that function
First, thank you for this project. I'm working on something simlar and your dissection of the binary sections eliminated all the heavy lifting for me.
I worked on a project similar to this recently which was to add an LDAP address book. I think this script may benefit from some of the logic. One suggestion is instead of nabbing the default profile, loop through the profile list and compare the email address from the encryption cert to the profile email
Second suggestion is to notify the user that Outlook needs to be restarted but only if Outlook is running, only if it's by the current user, and only if you modified the profile that Outlook has open. Of course this is only relevant if Outlook needs to be restarted for the changes. In my case, it was necessary