0

When I pass a byte array directly to he function it works as expected. When I create a new instance of my custom class and pass the same blob to the function, I get this error

FindInArray : Cannot process argument transformation on parameter 'source'. Cannot convert the "System.Byte[]" value
of type "System.Byte[]" to type "System.Byte".
At line:89 char:22
+             $pos = FindInArray($blob, $HashAsn[[HashAlgs]::$enum])
+                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [FindInArray], ParameterBindingArgumentTransformationException
    + FullyQualifiedErrorId : ParameterArgumentTransformationError,FindInArray

If I remove the type designation in the FindArray function definition, then it seems to work, but I don't think I should have to do that

In case it isn't clear: I want to have this class create a new instance either from nothing or from an existing blob. To parse an existing blob, I need to search the blob for instances of the enums. I'm trying to 'cheat' by working with the binary ASN1 data rather than decoding/encoding it

My code

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
}

[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 >:-[
$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
}

$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)

[Flags()] enum ESConfigOption {
    Default1 = 1
    Default2 = 2
    SendWithMsgs = 4
}


class ESAlgorithmAsn {
    [CryptAlgs]$EncryptionAlgorithm
    [CryptAlgs]$OtherEncryptionAlgorithms
    [HashAlgs]$HashAlgorithm
    [HashAlgs]$OtherHashAlgorithms
    
    ESAlgorithmAsn() {
        $this.EncryptionAlgorithm = [CryptAlgs]::AES_256
        $this.HashAlgorithm = [HashAlgs]::SHA_512
    }

    ESAlgorithmAsn([byte[]]$blob) {
        $CryptAsn = $Global:CryptAsn
        $HashAsn = $Global:HashAsn

        $firstEnum = 0
        $firstPos = -1
        
        foreach ($enum in [CryptAlgs].GetEnumNames()) {
            $pos = FindInArray($blob, $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
        $this.OtherEncryptionAlgorithms -= $firstEnum

        foreach ($enum in [HashAlgs].GetEnumNames()) {
            $pos = FindInArray([byte[]]$blob, $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
    }

    [byte[]]GetBytes() {
        $CryptAsn = $Global:CryptAsn
        $HashAsn = $Global:HashAsn

        [uint32]$size = 0
        $outputArray = $null
        $pos = 0
        $defaultHash = $null
        $defaultCrypt = $null
        
        if ($this.HashAlgorithm -eq 0) {throw 'Must include default hash algorithm'}
        if ($this.CryptAlgorithm -eq 0) {throw 'Must include default encryption algorithm'}
        if ($this.HashAlgorithm -band $this.OtherHashAlgorithms) {$this.OtherHashAlgorithms -= $this.HashAlgorithm}
        if ($this.CryptAlgorithm -band $this.OtherEncryptionAlgorithms) {$this.OtherEncryptionAlgorithms -= $this.CryptAlgorithm}
        
        foreach ($enum in [HashAlgs].GetEnumNames()) {
            if ($this.HashAlgorithm -band [HashAlgs]::$enum) {
                if ($defaultHash) {
                    throw 'Cannot set multiple default Hash Algorithms'
                } else {
                    $defaultHash = $HashAsn[[HashAlgs]::$this.HashAlgorithm]
                    $size += $defaultHash.Length
                }
            }
            if ($this.OtherHashAlgorithms -band [HashAlgs]::$enum){$size += $HashAsn[[HashAlgs]::$enum].length}
        }
        foreach ($enum in [CryptAlgs].GetEnumNames()) {
            if ($this.CryptAlgorithm -band [CryptAlgs]::$enum) {
                if ($defaultCrypt) {
                    throw 'Cannot set multiple default Encryption Algorithms'
                } else {
                    $defaultCrypt = $CryptAsn[[CryptAlgs]::$this.CryptAlgorithm]
                    $size += $defaultCrypt.Length
                }
            }
            if ($this.OtherEncryptionAlgorithms -band [CryptAlgs]::$enum) {$size += $CryptAsn[[CryptAlgs]::$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
        if ($size -lt 0x80) {
            $outputArray = [byte[]]::new($size + $pos)
            $outputArray[0] = [bitconverter]::GetBytes(0x30)[0]
            $outputArray[1] = [bitconverter]::GetBytes($size)[0]
        } else {
            #When total length is greater than 0x80, we have to set the greatest bit and use the
            #   rest of the byte to define how many bytes 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
            $iSize = 0
            foreach ($i in ($sizeBytes.Length - 1)..0) {if ($sizeBytes[$i]) {$iSize = $i; break}}

            $outputArray = [byte[]]::new($size + $pos + $iSize + 1)
            $outputArray[0] = [bitconverter]::GetBytes(0x30)[0]
            $outputArray[1] = [bitconverter]::GetBytes(0x80 + $iSize + 1)[0]

            # ASN1 stores numbers in Big-Endian so we need to reverse the array
            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.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.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
    }
}

And the test blob [byte[]]$blb = @(0x30, 0x5a, 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, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 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)

  • PowerShell functions, cmdlets, scripts, and external programs must be invoked _like shell commands_ - `foo arg1 arg2` - _not_ like C# methods - `foo('arg1', 'arg2')`. If you use `,` to separate arguments, you'll construct an _array_ that a command sees as a _single argument_. – mklement0 Nov 05 '22 at 15:29
  • See the [linked duplicate](https://stackoverflow.com/q/4988226/45375) and [this answer](https://stackoverflow.com/a/65208621/45375) for more information. – mklement0 Nov 05 '22 at 15:46
  • To spell it out in the context of your code: You must call your `FindInArray` function as follows: `FindInArray $blob $HashAsn[[HashAlgs]::$enum]`. If you mistakenly call it as `FindInArray($blob, $HashAsn[[HashAlgs]::$enum])`, as you did, you pass a _jagged array_ as a _single_ argument that PowerShell tries to bind to the `[byte[]`-typed `$source` parameter, causing it try to convert each nested array to `byte`, which predictably fails. – mklement0 Nov 05 '22 at 16:02
  • @mklement0 And just like that, it's working as expected. I feel like I should have caught that... Anyway, do you want to post it as an answer so I can accept it? – timothy-byles Nov 05 '22 at 16:21
  • Glad to hear it. Since the root problem is the same as in the linked duplicate, that link should be enough. – mklement0 Nov 05 '22 at 16:29

0 Answers0