Posts on this page:

Welcome to my new web home!

welcome

Finally I did it! I burned my old SharePoint blog and R.I.P.’ed my former Russian blog (DasBlog) as promised.

The problem

Just to recall the history. Several years ago I realized that I need a new website/portal to host my stuff. Initially I looked for a ready solution that would match my humble requirements:

  1. It should support hierarchical site directories to organize content;
  2. It should support SQL database;
  3. It should support multi-level hierarchical content categories;
  4. It should support an ability to control rendering;
  5. It should support old URLs and redirect to migrated content. It was very important, because I realize that there are tons reference links to my previous weblogs and can’t simply break them;
  6. Should use friendly URLs;
  7. Preferably, it should be .NET-based.

There were several candidates:

  • SharePoint (what? Again?)
  • Orchard
  • BlogEngine.NET
  • DasBlog (again)

After testing each from the list, neither matched all my requirements. I used to like Scott Hanselman’s DasBlog, as it matched almost all requirements, except first two. After that I decided to write my own blog/website engine with black-jack and hookers.

Solution

The first problem was that I don’t have web-application development experience. Many thanks to Sergey Zwezdin, who helped me to make first steps and provided valuable support during development. He suggested to try ASP.NET MVC + Entity Framework (EF) as a start point. Relatively quickly I learned basic concepts and some non-trivial things. Site development started about 2 years ago, but last year I realized that this is a bullshit, not a website and started again with new knowledge. Due to my studies at university, the progress was quite slow and only after exams I was able to work on website intensively.

The biggest conceptual problem was with hierarchical site directories, where each directory would have their own settings and filters. Once I implemented this, I faced another issue: ASP.NET routing failed to differentiate directories and pages with matching names. Actually, this was the only thing I couldn’t handle correctly and was forced to apply extensions to pages (like in classic WebForms). But I don’t care too much about this, not a big deal.

Second phase was existing content migration. Neither, DasBlog, nor SharePoint allows to export content in BlogML format. Since DasBlog uses XML to store site content, I quickly converted them to BlogML, including comments. With SharePoint this trick didn’t worked, so I used my small Metaweblog API client to grab posts from database.

Next phase was HTML content rendering control. I wanted to template some parts of page contents. For example, provide an unified PowerShell syntax highlighting, PowerShell console box rendering and so on. A great HtmlAgilityPack library became extremely handy to clear inline styles, transform HTML fetch images, rewrite embedded links to a new format and much more. In a fully automated way! I can guarantee that any direct link to any of my previously published page will work by redirecting to a new URL. Now this library is used to replace HTML at runtime. I wrote nice PowerShell syntax highlighter (by using built-in PowerShell tokenizer) and reused XML/HTML syntax highlighting from PS Cmdlet Help Editor and much more. JavaScript-free.

Once this major task was done, I started to work on related stuff, like Metaweblog API server (to post articles via Windows Live Writer), management system and UX. Eventually, I ended up with 5 MVC controllers and 5k+ (according to Visual Studio metrics) lines of code.

At this point, my new web site implements almost all my wishes, except the biggest one: site search. Unfortunately, there are no easy-to-use search engines for .NET applications, but I’m going to work on it. There are two search engines: Lucene.NET and Solr.NET, but they doesn’t look easy and requires some research.

What next?

There are still places to improve. My general roadmap includes:

  • Research an ability to add on-site search;
  • Replace heavy CKEditor with lightweight editor in the comment boxes;
  • Re-categorize existing content in main weblog;
  • Implement cuts for main page feed;
  • Move external content to this web site.

Final word

As a final word, I encourage you, my readers, to update bookmarks, links (if possible) to my website and RSS feed. For any questions, and/or issues, please, report them in the contact form.

And do not forget to visit my website for new content. Thank you, very much!

Certificate Rules may not work in Software Restriction Policies

SYMPTOMS

Consider the following scenario. You configured Software Restriction Policies (SRP) to allow run all applications that are signed by the specific signer by creating a Certificate Rule against the signer certificate.

When the policy is refreshed on the client, user cannot run the application, because it is blocked by Software Restriction Policies.

 

CAUSE

This behavior occurs when the certificate is issued by a Certification Authority (CA) which do not include or provide incorrect issuer information in the Authority Information Access (AIA) extension in the signing certificate. In addition, intermediate CA certificate is not installed in the local store. SRP reads only signing certificate in the digital signature and ignores the rest certificates.

This issue may occur when the signing certificate was issued by Thawte due to their CA certificate distribution policy.

STATUS

This behavior is by design. No bug fixes are available. See Workaround section for example steps to overcome the issue.

WORKAROUND

Perform the following steps to install intermediate CA certificate to local certificate store.

Option 1 is a general guidance for intermediate CA certificate retrieval. Option 2 is used only for signing certificates issued by Thawte.

Option 1

Option 2

The following steps can be used to save Thawte Code Signing CA – G2 certificate to a file:

  1. Run Notepad;
  2. Copy the following text and paste it to Notepad:

    -----BEGIN CERTIFICATE-----
    MIIEnDCCA4SgAwIBAgIQR5dNeHOlvKsNL7NwGS/OXjANBgkqhkiG9w0BAQUFADCB
    qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
    Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
    MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
    BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMTAwMjA4MDAwMDAwWhcNMjAw
    MjA3MjM1OTU5WjBKMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMVGhhd3RlLCBJbmMu
    MSQwIgYDVQQDExtUaGF3dGUgQ29kZSBTaWduaW5nIENBIC0gRzIwggEiMA0GCSqG
    SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3i891W58l2n45sJPbONOpI9CC+ukkflwL
    joP45npZ5qPFmKeZ0kT/AKalOQSK2imI6tui8xyZFSbCsfT84QxHqQkRBgogkrnH
    oASMXJQZq1slLB1ifnANzmFs3SuCyc5dSF/3wr68QSMeTyld10+89MUq/GPmfCZO
    mad5QZ4QSnp5ycaG94aV0ibOPBgq1nzOr82tu/eCLHAmN0XlD0cixgEovS6DXGqk
    R8Hn0NhrgUY/IRf1B8VDWqZnLLh7YBG1g+71dApycUQ9WP7oGqs4w1nbf244fXbH
    cmmYNpZX02Yc0lSRBC5UGbDcPbUiXobVKn4g313merFl/sUCTjEtAgMBAAGjggEc
    MIIBGDASBgNVHRMBAf8ECDAGAQH/AgEAMDQGA1UdHwQtMCswKaAnoCWGI2h0dHA6
    Ly9jcmwudGhhd3RlLmNvbS9UaGF3dGVQQ0EuY3JsMA4GA1UdDwEB/wQEAwIBBjAy
    BggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnRoYXd0ZS5j
    b20wHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMDMCkGA1UdEQQiMCCkHjAc
    MRowGAYDVQQDExFWZXJpU2lnbk1QS0ktMi0xMDAdBgNVHQ4EFgQU1A1lP3q9NMb+
    R+dMDcC98t4Vq3EwHwYDVR0jBBgwFoAUe1tFz6/Oy3r9MZIaarbzRutXSFAwDQYJ
    KoZIhvcNAQEFBQADggEBAFb+U1zhx568p+1+U21qFEtRjEBegF+qpOgv7zjIBMnK
    Ps/fOlhOsNS2Y8UpV/oCBZpFTWjbKhvUND2fAMNay5VJpW7hsMX8QU1BSm/Td8jX
    OI3kGd4Y8x8VZYNtRQxT+QqaLqVdv28ygRiSGWpVAK1jHFIGflXZKWiuSnwYmnmI
    ayMj2Cc4KimHdsr7x7ZiIx/telZM3ZwyW/U9DEYYlTsqI2iDZEHZAG0PGSQVaHK9
    xXFnbqxM25DrUaUaYgfQvmoARzxyL+xPYT5zhc5aCre6wBwTdeMiOSjdbR0JRp1P
    uuhAgZHGpM6UchsBzypuFWeVia59t7fN+Qo9dbZrPCU=
    -----END CERTIFICATE-----

  3. Click File menu and click Save or Save As...;
  4. In the Save dialog, select the path and file name. Append “.cer” extension to the end of the file name;
  5. In the Save as type drop-down list, select All Files (*.*);
  6. Make sure that Encoding drop-down list is set to ANSI:
    image
  7. Click Save button to save the file.

CA certificate installation instructions:

There are three options to install CA certificate to local certificate store:

  • Option 1 installs CA certificate to local machine only;
  • Option 2 installs CA certificate to a set of computers depending on Group Policy object scope in Active Directory;
  • Option 3 installs CA certificate to all machines in the current Active Directory forest

Option 1

  1. Log on to target computer with local Administrator permissions;
  2. Run Command Prompt in elevated mode;
  3. In the Command Prompt, type the following command:

    certutil -f –addstore CA path\cacertfile.cer

    change “path\cacert.cer” part with actual path and file name.
  4. Close Command Prompt.

Option 2

  1. Log on to a computer where Group Policy management tools are installed with Domain Admins permissions;
  2. Run Group Policy Management console (gpmc.msc);
  3. Edit existing or create a new Group Policy Object (GPO).
  4. In the GPO editor, expand Computer Configuration\Policies\Windows Settings\Security Settings\Public Key Infrastructure.
  5. Under Public Key Infrastructure node right-click on Intermediate Certification Authorities and click Import;
  6. Follow Certificate Import Wizard instructions to import CA certificate;
  7. Close GPO editor and Group Policy Management console.

When completed, refresh the group policy on the target clients by running “gpupdate /force” command.

Option 3

  1. Log on to any Active Directory member with Enterprise Administrator permissions;
  2. Run Command Prompt in elevated mode;
  3. In the Command Prompt, type the following command:

    certutil –dspublish –f path\cacertfile.cer SubCA

    change “path\cacert.cer” part with actual path and file name.
  4. Close command Prompt.

When completed, refresh the group policy on the target clients by running “gpupdate /force” command.

    Note: Option 3 requires that Autoenrollment policy is enabled on clients.


    APPLIES TO
    • Windows XP Professional
    • Windows Server 2003 (all editions)
    • Windows Vista Business, Ultimate, Enterprise
    • Windows Server 2008 (all editions)
    • Windows 7 Professional, Ultimate, Enterprise
    • Windows Server 2008 R2 (all editions)
    • Windows 8 Professional, Enterprise
    • Windows Server 2012 (all editions)
    • Windows 8.1 Professional, Enterprise
    • Windows Server 2012 R2 (all editions)

    Test PFX (PKCS#12) file and PFX password with CryptoAPI in PowerShell

    Today I want to present another useful CryptoAPI functions to use when working with PFX (PKCS#12) certificates.

    1. Determine if the BLOB is PFX without having to pass a password;
    2. Test PFX password.

    Of course, you can try to use appropriate X509Certificate2 class constructor, but this approach is faster and do not require key import in cryptographic provider and other actions performed by X509Certificate2 constructor. This functionality is implemented in two CryptoAPI functions:

    1. PFXIsPFXBlob
    2. PFXVerifyPassword

    And here is complete solution:

    $signature = @"
    [DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool PFXIsPFXBlob(
        CRYPTOAPI_BLOB pPFX
    );
    [DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool PFXVerifyPassword(
        CRYPTOAPI_BLOB pPFX,
        [MarshalAs(UnmanagedType.LPWStr)]
        string szPassword,
        int dwFlags
    );
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public struct CRYPTOAPI_BLOB {
        public int cbData;
        public IntPtr pbData;
    }
    "@
    Add-Type -MemberDefinition $signature -Namespace PKI -Name Crypt32
    
    function Test-CertIsPfx ([Byte[]]$RawData) {
        # create buffer in an unmanaged memory to store raw data
        $pbData = [Runtime.InteropServices.Marshal]::AllocHGlobal($RawData.Length)
        # copy bytes to unmanaged buffer:
        [Runtime.InteropServices.Marshal]::Copy($RawData,0,$pbData,$RawData.Length)
        # create CRYPTOAPI_BLOB structure
        $blob = New-Object PKI.Crypt32+CRYPTOAPI_BLOB -Property @{
            cbData = $RawData.Length
            pbData = $pbData
        }
        # call the PFXVerifyPassword to test the content. The function returns $true
        # if byte array represents PFX file.
        [PKI.Crypt32]::PFXIsPFXBlob($blob)
        # release unmanaged buffer:
        [Runtime.InteropServices.Marshal]::FreeHGlobal($pbData)
    }
    function Test-PfxPassword ([Byte[]]$RawData, [string]$Password) {
        # create buffer in an unmanaged memory to store raw data
        $pbData = [Runtime.InteropServices.Marshal]::AllocHGlobal($RawData.Length)
        # copy bytes to unmanaged buffer:
        [Runtime.InteropServices.Marshal]::Copy($RawData,0,$pbData,$RawData.Length)
        # create CRYPTOAPI_BLOB structure
        $blob = New-Object PKI.Crypt32+CRYPTOAPI_BLOB -Property @{
            cbData = $RawData.Length
            pbData = $pbData
        }
        # call the PFXVerifyPassword to test the password. The function returns $true
        # if password is correct
        [PKI.Crypt32]::PFXVerifyPassword($blob, $Password, 0)
        # release unmanaged buffer:
        [Runtime.InteropServices.Marshal]::FreeHGlobal($pbData)
    }

    and usage:

    [↓] [vPodans] $bytes = [io.file]::ReadAllBytes(".\3.pfx")
    [↓] [vPodans] $bytes2 = [io.file]::ReadAllBytes(".\pem.txt")
    [↓] [vPodans] Test-CertIsPfx $bytes
    True
    [↓] [vPodans] Test-CertIsPfx $bytes2
    False
    [↓] [vPodans] Test-PfxPassword $bytes "1"
    True
    [↓] [vPodans] Test-PfxPassword $bytes "other password"
    False
    [↓] [vPodans]

    Note: PFX files must be stored in a pure binary format. Unlike regular certificates, PFX do not support base64 encoding in files.

    In these examples, I read two files, one PFX, another – “whateverelse” and passed them to Test-CertIsPfx function. As you see, only bytes from “3.pfx” file returned $true. And attempted to guess the password and guessed from the first attempt!!! (joke). This is something you can write during morning coffee/tea :)

    Convert data between binary, hex and Base64 in PowerShell

    Point Of Interest

    Yesterday I asked in Twitter, who can convert byte array to a formatted hex string in PowerShell in 5 minutes. I got one solution with the reference to Format-Hex function. Then I asked opposite question: can you convert formatted hex dump with address and ASCII panes back to byte array in PowerShell in 5 minutes? Didn’t get any response.

    This subject is interesting and sometimes is necessary. Due to my specialization (cryptography), I have to deal with these formats often. And not only hex, Base64 with and without headers as well.

    Common formatting examples

    To make the subject clear, I provide some formatted examples:

    • Unformatted raw hex string:
    3081bd308198300906052b0e03021a0500304731133011060a0992268993f22c6401191603636f
    6d31173015060a0992268993f22c6401191607636f6e746f736f311730150603550403130e636f
    6e746f736f2d4443322d4341170d3130303330363131313033315a170d31353033303531313130
    33315a302430220203123456170d3132303732343134323730355a300c300a0603551d1504030a
    0103300906052b0e03021a05000315000e0df85638f094d170d905744f045d1287dff3df
    • Formatted hex string, 16 octets per row:
    30 81 bd 30 81 98 30 09  06 05 2b 0e 03 02 1a 05
    00 30 47 31 13 30 11 06  0a 09 92 26 89 93 f2 2c
    64 01 19 16 03 63 6f 6d  31 17 30 15 06 0a 09 92
    • Hex string with address and ASCII values:
    0000	30 81 bd 30 81 98 30 09  06 05 2b 0e 03 02 1a 05   0..0..0...+.....
    0010	00 30 47 31 13 30 11 06  0a 09 92 26 89 93 f2 2c   .0G1.0.....&...,
    0020	64 01 19 16 03 63 6f 6d  31 17 30 15 06 0a 09 92   d....com1.0.....
    • Raw Base64
    MIG9MIGYMAkGBSsOAwIaBQAwRzETMBEGCgmSJomT8ixkARkWA2NvbTEXMBUGCgmS
    JomT8ixkARkWB2NvbnRvc28xFzAVBgNVBAMTDmNvbnRvc28tREMyLUNBFw0xMDAz
    • Base64 with header:
    -----BEGIN XYZ-----
    MIG9MIGYMAkGBSsOAwIaBQAwRzETMBEGCgmSJomT8ixkARkWA2NvbTEXMBUGCgmS
    JomT8ixkARkWB2NvbnRvc28xFzAVBgNVBAMTDmNvbnRvc28tREMyLUNBFw0xMDAz
    -----END XYZ-----

    Converting the byte array to one of these formats may be quite challenging. But real challenge is to convert formatted data back to binary copy.

    CryptoAPI or cheat sheet

    To work this out, you have to check and, most likely, significantly upgrade your string parsing skills in PowerShell. But it is worth to mention Windows built-in solutions which can be utilized in PowerShell.

    Two things to know:

    1. Bad thing: we will deal with Win32 native functions
    2. Good thing: it will take just few lines of code :)

    We will need two functions:

    At first, let’s write unmanaged function signatures:

    $signature = @"
    [DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool CryptStringToBinary(
        string pszString,
        int cchString,
        int dwFlags,
        byte[] pbBinary,
        ref int pcbBinary,
        int pdwSkip,
        ref int pdwFlags
    );
    [DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool CryptBinaryToString(
        byte[] pbBinary,
        int cbBinary,
        int dwFlags,
        StringBuilder pszString,
        ref int pcchString
    );
    "@
    Add-Type -MemberDefinition $signature -Namespace PKI -Name Crypt32 -UsingNamespace "System.Text"
    $encoding = @{'Base64Header' = 0; 'Base64' = 1; 'HexRaw' = 12; 'Hex' = 4; 'HexAddr' = 10;
        'HexAscii' = 5; 'HexAddrAscii' = 11}

    Tip: I used a simplified encoding set for brevity.

    The signatures are fairly simple. Key notes about these functions:

    • Functions returns Boolean which tells whether the input string is in the same format being requested in the dwFlags parameter.
    • These functions require two calls, where first call returns the size for return value.
    • Return value size and actual value is returned in the pcbBinary and pbBinary parameters, respectively, in the CryptStringToBinary function.
    • Return value size and actual value is returned in the pszString and pcchString parameters, respectively, in the CryptBinaryToString function.
    • flags CRYPT_STRING_ANY, CRYPT_STRING_HEX_ANY and CRYPT_STRING_BASE64_ANY are not supported in CryptBinaryToString functions.

    Binary to string. Examples

    Now we will look into some examples. The full sample code with comments:

    # say, it is our byte array
    [Byte[]]$array = 0..49
    # initialize variable to receive resulting string length
    $pcchString = 0
    # call the CryptBinaryToString function to get string length. pszString is $null for the first call
    if ([PKI.Crypt32]::CryptBinaryToString($array,$array.Length,$encoding['Base64'],$null,[ref]$pcchString)) {
        # instantiate a StringBuilder object with required size
        $SB = New-Object Text.StringBuilder $pcchString
        # call the function again and pass StringBuilder into pszString parameter
        [void][PKI.Crypt32]::CryptBinaryToString($array,$array.Length,$encoding['Base64'],$sb,[ref]$pcchString)
        # display produced output
        $SB.ToString()
    } else {
        Write-Warning $((New-Object ComponentModel.Win32Exception ([Runtime.InteropServices.Marshal]::GetLastWin32Error())).Message)
    }

    When using the code for different formats, only dwFlags parameter is changed in both function calls. And here is the output for different formats:

    • To Base64
    AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v
    MDE=
    • To raw hex string
    000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031
    • To hex with 16 octets per row
    00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f
    10 11 12 13 14 15 16 17  18 19 1a 1b 1c 1d 1e 1f
    20 21 22 23 24 25 26 27  28 29 2a 2b 2c 2d 2e 2f
    30 31
    • To hex with address pane
    0000    00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f
    0010    10 11 12 13 14 15 16 17  18 19 1a 1b 1c 1d 1e 1f
    0020    20 21 22 23 24 25 26 27  28 29 2a 2b 2c 2d 2e 2f
    0030    30 31
    • To hex with ASCII pane
    00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f   ................
    10 11 12 13 14 15 16 17  18 19 1a 1b 1c 1d 1e 1f   ................
    20 21 22 23 24 25 26 27  28 29 2a 2b 2c 2d 2e 2f    !"#$%&'()*+,-./
    30 31                                              01
    • To hex with both, address and ASCII panes
    0000    00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f   ................
    0010    10 11 12 13 14 15 16 17  18 19 1a 1b 1c 1d 1e 1f   ................
    0020    20 21 22 23 24 25 26 27  28 29 2a 2b 2c 2d 2e 2f    !"#$%&'()*+,-./
    0030    30 31                                              01

    fast, quick, elegant!

    String to binary. Examples

    You can say that binary to hex is very simple, but opposite conversion is more sophisticated. You may need to determine whether the hex dump contains address and/or ASCII panes and ignore them during conversion. Hopefully, CryptStringToBinary function will take care of them. Here is another function to convert formatted string to a byte array:

    function Convert-HexToBinary ([string]$hex) {
        # decoding hashtable contains universal flags: Base64Any and HexAny. The function attempts to
        # get the correct input string format and then decode it
        $decoding = @{'Base64Header' = 0; 'Base64' = 1; 'HexRaw' = 12; 'Hex' = 4; 'HexAddr' = 10;
            'HexAscii' = 5; 'HexAddrAscii' = 11; 'Base64Any' = 6; 'HexAny' = 8}
        # initialize variables to receive resulting byte array size and actual input string format
        $pcbBinary = 0
        $pdwFlags = 0
        # call CryptStringToBinary to get resulting byte array size and actual input string format
        if ([PKI.Crypt32]::CryptStringToBinary($hex,$hex.Length,$decoding['HexAny'],$null,[ref]$pcbBinary,0,[ref]$pdwFlags)) {
            # create enough large byte array
            $array = New-Object byte[] -ArgumentList $pcbBinary
            # call the function again to write converted bytes to a byte array
            [void][PKI.Crypt32]::CryptStringToBinary($hex,$hex.Length,$decoding['HexAny'],$array,[ref]$pcbBinary,0,[ref]$pdwFlags)
        } else {
            Write-Warning $((New-Object ComponentModel.Win32Exception ([Runtime.InteropServices.Marshal]::GetLastWin32Error())).Message)
        }
    }

    Look at this:

    [↓] [vPodans] $string = @"
    >> 0000    00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f
    >> 0010    10 11 12 13 14 15 16 17  18 19 1a 1b 1c 1d 1e 1f
    >> 0020    20 21 22 23 24 25 26 27  28 29 2a 2b 2c 2d 2e 2f
    >> 0030    30 31
    >> "@
    >>
    [↓] [vPodans] $array = Convert-HexToBinary $string
    Input string encoding: 10
    [↓] [vPodans] $array[0..10]
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [↓] [vPodans] $array.Length
    50
    [↓] [vPodans]

    As you can see, we got our original byte array! Exactly 50 bytes! I showed the $pdwFlags variable which stores actual format type of input string. 10 stands for CRYPT_STRING_HEXADDR. Don’t believe? Check CryptStringToBinary function description page.

    You think it is all? Hell no! If we put comments, will it work?

    [↓] [vPodans] $string = @"
    >> 0000    00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f ;line one
    >> 0010    10 11 12 13 14 15 16 17  18 19 1a 1b 1c 1d 1e 1f ; line two
    >> 0020    20 21 22 23 24 25 26 27  28 29 2a 2b 2c 2d 2e 2f ; FF BB
    >> 0030    30 31
    >> "@
    >>
    [↓] [vPodans]  $array = Convert-HexToBinary $string
    Input string encoding: 11
    [↓] [vPodans] $array[0..10]
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [↓] [vPodans] $array.Length
    50
    [↓] [vPodans]

    The function is enough smart. Exactly 50 bytes. Though, the function identified the string as hex string with address and ASCII panes. But the result is achieved!

    Final word

    Today we explored another powerful and useful feature from Win32 repository. Decent google/bing skills and an ability to write native function signature definitions in PowerShell allows you to solve this task in 5 minutes! Not all love Win32, but sometimes it simply saves many hours of your work. Spend it wisely!

    HTH

    PowerShell PKI module, v3.1

    Yesterday I released another version of PowerShell PKI module v3.1.

    Though, this release is not that big like v3.0. Only 1 (one!) new command is added (Get-EnterprisePKIHealthStatus) and various bug fixes (as usually). On the other hand it includes very important things which are hidden behind the scene.

    The project is growing and I have to battle hard with poor design decisions I made previously. Of course, I could break everything and make it as per all guidelines. However, it is too late, 3000 downloads for the past 7 months is not a joke and I can’t simply break it.

    Next sections will cover some development details, so you can scroll down to the end of post to get the right link :)

    ASN.1 type system

    First, I almost polished my ASN.1 parser, it is now rewritten by using more efficient code. For one function I was forced to look for some help on StackOverflow. The lesson is learned and now I have fairly efficient code where it matters most. You can ask, why I pay so much attention to that ASN.1, while even PKI administrators (not to tell about other systems administrators and PowerShell users) have a very-very little knowledge about ASN and do not use it directly. The truth is that entire Internet PKI communication system is based on ASN.1. What is your digital certificate? It is ASN.1. What is your certificate request? It is ASN.1. What is PKCS#X? It is ASN.1. An ability to read, decode, encode ASN.1 data is a “must have” for any PKI developer. CryptoAPI has a rich support of ASN.1, but .NET do not. Although I like CryptoAPI as it can do things in few lines and very quickly while managed implementation is bigger and slower, I decided to move common classes to managed code. Mostly because of unmanaged memory management. When you create CRL object, its handle is not released in normal ways and users do not release them as well.

    I’m planning to extend my own ASN.1 type system support by adding classes for each universal tag. Currently, only two classes are exposed: http://pkix2.sysadmins.lv/library/html/N_PKI_ASN_Universal.htm, but there are a bit more (currently marked private):

    image

    In addition, entire ASN namespace will be removed from PKI.Core.dll library to its own assembly, because I’m actively using it in other projects.

    X509Certificates namespace

    I’m constantly adding new certificate/CRL extension support (both, Microsoft-specific and Internet PKI) in the X509Certificates namespace to provide ease access to extension data. Worth to mentioned that I rewrote X509CRLDistributionPointsExtension class to fully match RFC5280 specifications. Now this class can decode partitioned and non-standard (though, within RFC standard) CDP extension. Although, this extension now looks a bit complicated, a shortcut method GetURLs helps you to easily access URLs in the extension.

    Check this namespace with each release, maybe you can find there something useful for you.

    Another feature (planned) is to add an ability to read certificate properties when they are installed in the store. Sometimes they may have interesting details. For example, information about private key, enrollment options and so on:

    image

    Note that not all details are shown in the certificate UI window. I’m thinking about the best way to implement it, but would be a great feature

    Roadmap

    With this release, I officially published an official project development roadmap. It reflects my personal vision of module extension with added community requests I consider important or interesting.

    Download

    >> PowerShell PKI v3.1 <<