Imports System
Imports System.Security.Cryptography

Public Class PackageAuth
    ' constants
    Private Const OFFSET_SECT1 = &H22C
    Private Const OFFSET_SECT2 = &H344

    Private ErrorString As String
    Private HeaderLoaded As Boolean
    Private KeyVaultLoaded As Boolean

    Private RSA As New RSACryptoServiceProvider()
    Private SHA As New SHA1CryptoServiceProvider()

    ' CON file data
    Private MagicInt(3) As Byte
    Private PublicKey(&H1A7) As Byte
    Private SaveSignature(&H7F) As Byte
    Private FirstHdrHashedBlock(&H117) As Byte
    Private SecondHdrHashedBlock(&H9CBB) As Byte

    Public Sub New()
        HeaderLoaded = False
        KeyVaultLoaded = False
    End Sub

    Public Function IsHeaderLoaded() As Boolean
        Return HeaderLoaded
    End Function

    Public Function IsKeyVaultLoaded() As Boolean
        Return KeyVaultLoaded
    End Function

    Public Function GetError() As String
        Return ErrorString
    End Function

    ' return 1 if success, 0 if file invalid, -1 if error
    Public Function LoadHeader(ByRef CONHeader As Byte()) As Integer
        Dim HeaderHash(&H13) As Byte
        Dim HashTableHash(&H13) As Byte
        Dim ImportedPublicKey(&H1A7) As Byte

        ' check it's a CON file
        If System.Text.Encoding.Default.GetString(CopyArrayPart(CONHeader, 0, 4)) <> "CON " Then
            ErrorString = "File could not be opened: Not CON file"
            Return -1
        End If

        ' Check the length of the header
        If CONHeader.GetLength(0) <> &HA000 Then
            ErrorString = "Error opening CON file: Invalid header length"
            Return -1
        End If

        ' load the file parts
        MagicInt = CopyArrayPart(CONHeader, 0, 4)
        ImportedPublicKey = CopyArrayPart(CONHeader, 4, &H1A8)
        SaveSignature = CopyArrayPart(CONHeader, &H1AC, SaveSignature.Length)
        FirstHdrHashedBlock = CopyArrayPart(CONHeader, &H22C, FirstHdrHashedBlock.Length)
        HeaderHash = CopyArrayPart(CONHeader, &H32C, HeaderHash.Length)
        SecondHdrHashedBlock = CopyArrayPart(CONHeader, &H344, SecondHdrHashedBlock.Length)
        'HashTableHash = STFSMasterHash 'CopyArrayPart(CONHeader, &H381, HashTableHash.Length)

        HeaderLoaded = True

        ' check the CON console key
        If VerifyConsoleKey(ImportedPublicKey) = 1 Then
            ErrorString = "CON file console key valid"
        Else
            ErrorString = "CON file inconsistency: unable to verify console key"
            Return 0
        End If

        ' check the save signature
        If VerifySaveSignature(SaveSignature, FirstHdrHashedBlock, ImportedPublicKey) = 1 Then
            ErrorString = "CON file save signature valid"
        Else
            ErrorString = "CON file inconsistency: unable to verify save signature"
            Return 0
        End If

        ' check the header hash
        If VerifyHash(SecondHdrHashedBlock, HeaderHash) Then
            ErrorString = "CON file header hash valid"
        Else
            ErrorString = "CON file inconsistency: unable to verify header hash"
            Return 0
        End If

        ErrorString = "CON file loaded successfully"

        Return 1
    End Function

    ' return 1 if success, 0 if file invalid, -1 if error
    Public Function LoadKeyVault(ByRef KVFilename As String) As Integer
        Dim RSAKeyInfo As New RSAParameters()
        ' CON private key components
        Dim CK_DP(&H3F) As Byte ' stored at 0x390
        Dim CK_DQ(&H3F) As Byte ' stored at 0x3D0
        Dim CK_Exponent(3) As Byte ' this is at offset 0x284
        Dim CK_InverseQ(&H3F) As Byte ' can't match this, but presumably it is the 0x40 bytes at 0x410
        Dim CK_Modulus(&H7F) As Byte 'this is at offset 0x290
        Dim CK_P(&H3F) As Byte 'this is at offset 0x310
        Dim CK_Q(&H3F) As Byte 'this is at offset 0x350
        ' below is a dummy, the value doesn't seeem to matter
        Dim CK_D As Byte() = {&H6D, &H4C, &HCF, &H3D, &HE8, &H65, &H51, &HFF, &H2D, &HAC, &HC1, &H90, &HE7, &H47, &HEB, &HC6, _
&H74, &H58, &HD0, &H2D, &H19, &H8, &HAC, &H79, &HCE, &HD0, &H1D, &HA3, &H1C, &HC3, &H2E, &H39, _
&H8E, &HC7, &HEF, &H66, &HFA, &HE4, &H2F, &H10, &H42, &HA8, &H4E, &HE7, &HA1, &HFD, &HF4, &HF0, _
&HCB, &H64, &H67, &HA6, &H10, &H4D, &H6D, &H3A, &H56, &H9D, &H1F, &HEC, &H51, &HFC, &HC2, &H26, _
&H45, &HC2, &HDE, &HF9, &H9B, &H4C, &H4C, &H93, &H4D, &HA8, &H2B, &H48, &HAC, &HED, &HD7, &HFC, _
&HEA, &HE9, &H72, &HFB, &HB2, &H39, &H88, &HC1, &H7, &H34, &H6F, &H2A, &H7, &H7E, &H97, &H81, _
&HF5, &H2, &H21, &HFA, &HCD, &HDD, &H30, &HDD, &HE5, &H41, &HB3, &H4A, &H22, &H73, &H80, &H89, _
&H2B, &H9E, &H90, &HAF, &HC4, &HA, &H8A, &H50, &H15, &HF, &HBD, &H6E, &HD4, &H95, &H37, &H79}
        Dim kvstore As New PackageStore()
        Dim KeyVault(&H3FE7) As Byte

        KeyVaultLoaded = False

        If kvstore.GetKeyVault(KVFilename, KeyVault) <> 1 Then
            ErrorString = kvstore.GetError()
            Return -1
        End If

        If KeyVault.Length <> 16360 Then
            ErrorString = "KeyVault not loaded: Invalid Length"
            Return -1
        End If

        CK_Exponent = CopyArrayPart(KeyVault, &H284, 4)

        CK_Modulus = CopyArrayPart(KeyVault, &H290, &H80)
        ReverseInBlocks(CK_Modulus)

        CK_P = CopyArrayPart(KeyVault, &H310, &H40)
        ReverseInBlocks(CK_P)

        CK_Q = CopyArrayPart(KeyVault, &H350, &H40)
        ReverseInBlocks(CK_Q)

        CK_DP = CopyArrayPart(KeyVault, &H390, &H40)
        ReverseInBlocks(CK_DP)

        CK_DQ = CopyArrayPart(KeyVault, &H3D0, &H40)
        ReverseInBlocks(CK_DQ)

        ' this bit is questionable; can't be matched; also won't work if P and Q were interpreted the wrong way around
        CK_InverseQ = CopyArrayPart(KeyVault, &H410, &H40)
        ReverseInBlocks(CK_InverseQ)

        RSAKeyInfo.Exponent = CK_Exponent
        RSAKeyInfo.Modulus = CK_Modulus
        RSAKeyInfo.P = CK_P
        RSAKeyInfo.Q = CK_Q
        RSAKeyInfo.DP = CK_DP
        RSAKeyInfo.DQ = CK_DQ
        RSAKeyInfo.InverseQ = CK_InverseQ
        RSAKeyInfo.D = CK_D

        Try
            RSA.ImportParameters(RSAKeyInfo)
        Catch ex As Exception
            ErrorString = "Error loading keyvault data"
            Return -1
        End Try

        ' update the ImportedPublicKey
        Array.Copy(KeyVault, &H9B0, PublicKey, 0, PublicKey.Length)

        ErrorString = "Keyvault loaded successfully"

        KeyVaultLoaded = True

        Return 1
    End Function

    ' return 1 if success, 0 if file invalid, -1 if error
    Public Function UpdateHeader(ByRef HeaderToUpdate As Byte()) As Integer
        Dim RSAFormatter As New RSAPKCS1SignatureFormatter(RSA)
        Dim Hash(13) As Byte

        ' first check that the header has been loaded
        If Not HeaderLoaded Or Not KeyVaultLoaded Then
            ErrorString = "Method must be called AFTER the load method has performed successfully"
            Return -1
        End If

        ' update the console ID
        Array.Copy(PublicKey, 2, SecondHdrHashedBlock, &H36C - OFFSET_SECT2, 5)

        ' update the header hash
        Array.Copy(GenerateHash(SecondHdrHashedBlock), 0, FirstHdrHashedBlock, &H32C - OFFSET_SECT1, &H14)

        ' update the signature
        RSAFormatter.SetHashAlgorithm("SHA1")
        Hash = GenerateHash(FirstHdrHashedBlock)
        Try
            SaveSignature = RSAFormatter.CreateSignature(Hash)
        Catch ex As Exception
            Return -1
        End Try

        ' update the console id
        Array.Copy(PublicKey, 2, SecondHdrHashedBlock, &H36C - OFFSET_SECT2, 5)

        ' transform file signature
        Array.Reverse(SaveSignature)

        ' update the header
        Array.Copy(MagicInt, 0, HeaderToUpdate, 0, MagicInt.Length)
        Array.Copy(PublicKey, 0, HeaderToUpdate, 4, PublicKey.Length)
        Array.Copy(SaveSignature, 0, HeaderToUpdate, &H1AC, SaveSignature.Length)
        Array.Copy(FirstHdrHashedBlock, 0, HeaderToUpdate, &H22C, FirstHdrHashedBlock.Length)
        Array.Copy(SecondHdrHashedBlock, 0, HeaderToUpdate, &H344, SecondHdrHashedBlock.Length)

        If HeaderToUpdate.Length <> &HA000 Then
            Return -1
        End If

        Return 1
    End Function

    ' returns 1 if valid, 0 if invalid, -1 if error
    Public Function VerifyConsoleKey(ByRef ConsoleImportedPublicKey As Byte()) As Integer
        Dim RSAKeyInfo As New RSAParameters()
        Dim ConsoleSignature As Byte() = {}
        Dim SignedData As Byte() = {}
        Dim DecryptedSignature As Byte() = {}
        Dim Hash(13) As Byte
        Dim Exponent As Byte() = {&H0, &H0, &H0, &H3}
        Dim ConsoleKeyVerificationImportedPublicKey As Byte() = _
        {&HDD, &H5F, &H49, &H6F, &H99, &H4D, &H37, &HBB, &HE4, &H5B, &H98, &HF2, &H5D, &HA6, &HB8, &H43, _
        &HBE, &HD3, &H10, &HFD, &H3C, &HA4, &HD4, &HAC, &HE6, &H92, &H3A, &H79, &HDB, &H3B, &H63, &HAF, _
        &H38, &HCD, &HA0, &HE5, &H85, &H72, &H1, &HF9, &HE, &H5F, &H5A, &H5B, &H8, &H4B, &HAD, &HE2, _
        &HA0, &H2A, &H42, &H33, &H85, &H34, &H53, &H83, &H1E, &HE5, &H5B, &H8F, &HBF, &H35, &H8E, &H63, _
        &HD8, &H28, &H8C, &HFF, &H3, &HDC, &HC4, &H35, &H2, &HE4, &HD, &H1A, &HC1, &H36, &H9F, &HBB, _
        &H90, &HED, &HDE, &H4E, &HEC, &H86, &H10, &H3F, &HE4, &H1F, &HFD, &H96, &HD9, &H3A, &H78, &H25, _
        &H38, &HE1, &HD3, &H8B, &H1F, &H96, &HBD, &H84, &HF6, &H5E, &H2A, &H56, &HBA, &HD0, &HA8, &H24, _
        &HE5, &H2, &H8F, &H3C, &HA1, &H9A, &HEB, &H93, &H59, &HD7, &H1B, &H99, &HDA, &HC4, &HDF, &H7B, _
        &HD0, &HC1, &H9A, &H12, &HCC, &H3A, &H17, &HBF, &H6E, &H4D, &H78, &H87, &HD4, &H2A, &H7F, &H6B, _
        &H9E, &H2F, &HCD, &H8D, &H4E, &HF5, &HCE, &HC2, &HA0, &H5A, &HA3, &HF, &H9F, &HAD, &HFE, &H12, _
        &H65, &H74, &H20, &H6F, &HF2, &H5C, &H52, &HE4, &HB0, &HC1, &H3C, &H25, &HD, &HAE, &HD1, &H82, _
        &H7C, &H60, &HD7, &H44, &HE5, &HCD, &H8B, &HEA, &H6C, &H80, &HB5, &H1B, &H7A, &HC, &H2, &HCE, _
        &HC, &H24, &H51, &H3D, &H39, &H36, &H4A, &H3F, &HD3, &H12, &HCF, &H83, &H8D, &H81, &H56, &H0, _
        &HB4, &H64, &H79, &H86, &HEA, &HEC, &HB6, &HDE, &H8A, &H35, &H7B, &HAB, &H35, &H4E, &HBB, &H87, _
        &HEA, &H1D, &H47, &H8C, &HE1, &HF3, &H90, &H13, &H27, &H97, &H55, &H82, &H7, &HF2, &HF3, &HAA, _
        &HF9, &H53, &H47, &H8F, &H74, &HA3, &H8E, &H7B, &HAE, &HB8, &HFC, &H77, &HCB, &HFB, &HAB, &H8A}

        ' verify the length of the console public key
        If ConsoleImportedPublicKey.GetLength(0) <> &H1A8 Then
            Return -1
        End If

        ' extract part of public key
        ConsoleSignature = CopyArrayPart(ConsoleImportedPublicKey, &HA8, &H100)
        SignedData = CopyArrayPart(ConsoleImportedPublicKey, 0, &HA8)

        ' apply transforms
        ReverseInBlocks(ConsoleKeyVerificationImportedPublicKey)
        Array.Reverse(ConsoleSignature)

        ' decrypt signature
        ' first set the parameters (only public encryption params necessary here)
        RSAKeyInfo.Modulus = ConsoleKeyVerificationImportedPublicKey
        RSAKeyInfo.Exponent = Exponent
        ' then import the data structure
        Try
            RSA.ImportParameters(RSAKeyInfo)
        Catch ex As Exception
            ErrorString = "Error validating console key"
            Return -1
        End Try

        ' compute hash
        Hash = SHA.ComputeHash(SignedData)

        ' then verify the signature
        If RSA.VerifyHash(Hash, "SHA1", ConsoleSignature) Then
            Return 1
        Else
            Return 0
        End If

        Return -1
    End Function

    ' returns 1 if valid, 0 if invalid, -1 if error
    Public Function VerifySaveSignature(ByRef Signature As Byte(), ByRef HashedData As Byte(), ByRef ImportedPublicKey As Byte()) As Integer
        Dim RSAKeyInfo As New RSAParameters()
        Dim Exponent(&H7F) As Byte
        Dim Modulus(3) As Byte

        Exponent = CopyArrayPart(ImportedPublicKey, &H24, 4)
        Modulus = CopyArrayPart(ImportedPublicKey, &H28, &H80)

        ReverseInBlocks(Modulus)
        Array.Reverse(Signature)

        RSAKeyInfo.Modulus = Modulus
        RSAKeyInfo.Exponent = Exponent

        RSA.ImportParameters(RSAKeyInfo)

        If RSA.VerifyData(HashedData, CryptoConfig.MapNameToOID("SHA1"), Signature) Then
            Return 1
        Else
            Return 0
        End If

        Return -1
    End Function

    ' returns 1 if valid, 0 if invalid, -1 if error
    Private Function VerifyHash(ByRef HashedData As Byte(), ByRef CurrentHash As Byte()) As Integer
        Dim Hash(13) As Byte

        Hash = SHA.ComputeHash(HashedData)

        If ArrayEquals(Hash, CurrentHash) Then
            Return 1
        Else
            Return 0
        End If

        Return -1
    End Function

    ' returns 1 if valid, 0 if invalid, -1 if error
    Private Function GenerateHash(ByRef HashedData As Byte()) As Byte()
        Dim Hash(13) As Byte

        Hash = SHA.ComputeHash(HashedData)

        Return Hash
    End Function

    Private Sub ReverseInBlocks(ByRef Key As Byte(), Optional ByVal BlockSize As Integer = 8)
        Dim Tmp As Byte
        Dim OldPos As Integer
        Dim NewPos As Integer
        Dim ByteCnt As Integer

        ' sanity check that the swap can be done
        If Key.GetLength(0) Mod (BlockSize) <> 0 Or Key.GetLength(0) < BlockSize Then
            ErrorString = "Key incorrect length for transform! (" & Key.Length.ToString & ")"
            Return
        End If

        'fill in the array
        For ByteCnt = 1 To Key.GetLength(0) \ 2
            OldPos = ByteCnt - 1
            NewPos = Key.GetLength(0) - ((OldPos \ BlockSize) * BlockSize) - BlockSize + OldPos Mod BlockSize
            Tmp = Key.GetValue(NewPos)
            Key.SetValue(Key.GetValue(OldPos), NewPos)
            Key.SetValue(Tmp, OldPos)
        Next
    End Sub

End Class
