/**@@@+++@@@@******************************************************************
**
** Microsoft Windows Media
** Copyright (C) Microsoft Corporation. All rights reserved.
**
***@@@---@@@@******************************************************************
*/

#include <drmcommon.h>
#include <drmcontextsizes.h>
#include <drmutilities.h>
#include <drmcrt.h>
#include <drmxmlparser.h>
#include <drmdevcert.h>
#include <drmpkcrypto.h>
#include <oemimpl.h>


#if DRM_SUPPORT_SYMMETRIC_OPTIMIZATIONS

/*********************************************************************
**
**  Function:  _VerifySymmerticSignature
**
**  Synopsis:  Verifies the symmtric signature of the device.  This uses the GCPrivkey.
**
**  Arguments:  
**     [f_pdstrCert] -- Certificate to verify
*********************************************************************/
static DRM_RESULT _VerifySymmerticSignature( IN DRM_CONST_STRING *f_pdstrCert )
{
    DRM_RESULT       dr               = DRM_SUCCESS;
    PRIVKEY          GCPrivKey        = { 0 };
    DRM_CONST_STRING dstrCertNodeData = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrDataNodeData = EMPTY_DRM_STRING; 
    DRM_CONST_STRING dstrInsert       = EMPTY_DRM_STRING;
    DRM_BYTE         rgbSymmSig[__CB_DECL(SHA_DIGEST_LEN)] = {0};
    DRM_DWORD        cbSymmSig = SIZEOF( rgbSymmSig );

    /* Extract the symmertic signature and verify it */
    ChkDR( DRM_XML_GetSubNode( f_pdstrCert, 
                              &g_dstrTagCertificate, 
                              &g_dstrAttributeType, 
                              &g_dstrTagDevice, 
                               0, 
                               NULL, 
                              &dstrCertNodeData, 
                               1) );
    
    ChkDR( DRM_XML_GetNode( &dstrCertNodeData, 
                            &g_dstrTagData, 
                             NULL, 
                             NULL, 
                             0, 
                            &dstrDataNodeData, 
                             NULL) );

    ChkDR( DRM_XML_GetNode( &dstrCertNodeData, 
                            &g_dstrTagSymSig, 
                             NULL, 
                             NULL, 
                             0, 
                             NULL, 
                            &dstrInsert));


    ChkDR( DRM_B64_DecodeW( &dstrInsert, &cbSymmSig, rgbSymmSig, 0 ) );
                
    ChkDR( OEM_GetGroupCertPrivateKey( &GCPrivKey ) );
    ChkDR( DRM_PK_SymmetricVerify( (DRM_BYTE*)&GCPrivKey,
                                    SIZEOF( GCPrivKey ),
                                    PB_DSTR(&dstrDataNodeData),
                                    CB_DSTR(&dstrDataNodeData),
                                    rgbSymmSig ) );
    dr = DRM_SUCCESS;
ErrorExit:
    ZEROMEM( &GCPrivKey, sizeof( PRIVKEY ) );
    return dr;
}

#endif /* DRM_SUPPORT_SYMMETRIC_OPTIMIZATIONS */

/* =============================================================================
** Function:    DRM_DC_CreateDevcert
** Synopsis:    Create Devcert from given DevcertTemplate
** Arguments:   [pDevCertTemplate] -- devcert template string
**              [f_pcontextCRYP]   -- 
** Returns:     DRM_SUCCESS on success
** Notes:       
** =============================================================================
*/
DRM_RESULT DRM_API DRM_DDC_GetDeviceCertificate(
    IN OUT DRM_STRING         *f_pdstrDevCert,
    IN     DRM_DWORD           f_dwFlags,
    IN OUT DRM_CRYPTO_CONTEXT *f_pcontextCRYP )
{
    DRM_RESULT       dr = DRM_SUCCESS;
    DRM_DWORD        cbDevCert     = 0;
    DRM_DWORD        cchMaxDevCert = 0;
    DRM_DWORD        cchUniqueID   = 0;
    PRIVKEY          GCPrivKey     = { 0 };
    PRIVKEY          FBSigningKey  = { 0 };
    DRM_CONST_STRING dstrCertNodeData = EMPTY_DRM_STRING;
    DRM_CONST_STRING dstrDataNodeData = EMPTY_DRM_STRING; 
    DRM_CONST_STRING dstrInsert       = EMPTY_DRM_STRING;

    DRM_PROFILING_ENTER_SCOPE(L"DRM_DDC_GetDeviceCertificate", g_pwszEnteringFunction, DRM_PROFILING_DONT_START);

    ChkArg( f_pcontextCRYP != NULL);
    ChkDRMString( f_pdstrDevCert );

    cchMaxDevCert = f_pdstrDevCert->cchString;
    cbDevCert     = CB_DSTR( f_pdstrDevCert );    

    /*First try to get device certificate*/
    dr = OEM_GetDeviceCert( PB_DSTR( f_pdstrDevCert ), &cbDevCert ) ;
    if( DRM_SUCCEEDED( dr ) )
    {
        f_pdstrDevCert->cchString = cbDevCert / SIZEOF( DRM_WCHAR );

        /* return if flag not set */
        if ( !(f_dwFlags & DRM_DCP_VERIFY_DEVCERT ) )
        {
            goto ErrorExit;
        }

#if DRM_SUPPORT_SYMMETRIC_OPTIMIZATIONS
        dr = _VerifySymmerticSignature( (DRM_CONST_STRING*) f_pdstrDevCert );
#else
        dr = DRM_DCP_VerifyDeviceCert((DRM_CONST_STRING*)f_pdstrDevCert,
                                       DRM_DCP_VERIFY_DEVICE_CERT,
                                       f_pcontextCRYP);
#endif

        if ( DRM_SUCCEEDED(dr) )
        {
            goto ErrorExit;
        }

        /* if we reach here, the devcert is no good and we need to create a new one from template */
    }

    if( !(f_dwFlags & DRM_DCP_CREATE_DEVCERT_IF_NOT_EXISTING) )
    {
        ChkDR( DRM_E_DEVCERTREADERROR );
    }

    /* Certificate didn't exist or no good.  Create one. */

    /* 
    ** Get devcert template. And check the size. If size of Template is 
    ** greater than MAX_DEVICE_CERT_TEMPLATE_SIZE, return error
    */
    cbDevCert = min(MAX_DEVICE_CERT_TEMPLATE_SIZE, cchMaxDevCert * SIZEOF(DRM_WCHAR));
    dr = OEM_GetDeviceCertTemplate( PB_DSTR( f_pdstrDevCert ),  &cbDevCert );
    if( dr == DRM_E_BUFFERTOOSMALL )
    {
        ChkDR( DRM_E_INVALIDDEVICECERTIFICATETEMPLATE );
    }
    else if( DRM_FAILED( dr ) )
    {
        /*Its a fatal error.*/
        ChkDR( DRM_E_DEVCERTREADERROR );
    }
    
    f_pdstrDevCert->cchString = cbDevCert / SIZEOF( DRM_WCHAR );

    /* 
    ** Don't bother to verify the signature of the DAC and GC certificates.
    ** If they are bad there is no recovery anyway.  Upstream applications will 
    ** (PC and WMRM) will validate this before issuing licenses anyway.
    */
    
    /* calc size needed */
    dr = OEM_GetUniqueID(NULL, &cchUniqueID);
    if (dr != DRM_E_BUFFERTOOSMALL)
    {
        ChkDR(dr);
    }
    f_pdstrDevCert->cchString = f_pdstrDevCert->cchString
                              + cchUniqueID                                               /* size of B64 encoded unique id */
                              + CCH_BASE64_EQUIV(PK_ENC_PUBLIC_KEY_LEN)                   /* size of B64 encoded Device Pubkey */
                              + CCH_BASE64_EQUIV(SIZEOF(PRIVKEY) + PK_ENC_CIPHERTEXT_LEN) /* size of B64 encoded encrypted Device Privkey */
                              + CCH_BASE64_EQUIV(SIZEOF(PKCERT))                          /* size of B64 encoded PKCERT */
#if DRM_SUPPORT_SYMMETRIC_OPTIMIZATIONS
                              + CCH_BASE64_EQUIV(SHA_DIGEST_LEN)                          /* size of B64 encoded symmetric signature of device/data node */
#endif
                              + CCH_BASE64_EQUIV(PK_ENC_SIGNATURE_LEN);                   /* size of B64 encoded signature of device/data node */
                              
    if ( cchMaxDevCert < f_pdstrDevCert->cchString )
    {
        ChkDR(DRM_E_BUFFERTOOSMALL);
    }

    /* 
    ** Generate device unique ID and
    ** fill in uniqueID in "DEVCERT/CERTIFICATE type="DEVICE"/DATA/UNIQUEID" 
    */
    ChkDR(DRM_DCP_GetAttribute((DRM_CONST_STRING*)f_pdstrDevCert, DRM_DEVCERT_SERIALNUMBER, NULL, &dstrInsert));
    ChkDR(DRM_UTL_StringRemoveSubString(f_pdstrDevCert, &dstrInsert));
    ChkDR(DRM_UTL_StringInsertBlankSubString( f_pdstrDevCert,
            (DRM_DWORD)(dstrInsert.pwszString - f_pdstrDevCert->pwszString),
            cchUniqueID));
    ChkDR(OEM_GetUniqueID((DRM_WCHAR*)dstrInsert.pwszString, &cchUniqueID));


    /* 
    ** Fill in Pubkey in DEVCERT/CERTIFICATE type="DEVICE"/DATA/PUBLICKEY 
    */
    {
        PUBKEY           GCPubkey = { 0 };       /* parse from GC <DATA>/<PUBLICKEY> */
        DRM_CONST_STRING dstrPubkey  = EMPTY_DRM_STRING; 
        DRM_CONST_STRING dstrPrivkey = EMPTY_DRM_STRING; 
        DRM_DWORD        cbDestination = SIZEOF(GCPubkey);
        DRM_BYTE        *pbTmp = NULL;

        /* locate GC Pubkey */
        ChkDR(DRM_DCP_GetAttribute((DRM_CONST_STRING*)f_pdstrDevCert, DRM_DEVCERT_GROUPCERTPUBKEY, NULL, &dstrInsert));
        DRM_B64_DecodeW(&dstrInsert, &cbDestination, (DRM_BYTE*)&GCPubkey, 0);

        /* Make room for Device Pubkey in DEVCERT/CERTIFICATE type="DEVICE"/DATA/PUBKEY*/
        ChkDR(DRM_DCP_GetAttribute((DRM_CONST_STRING*)f_pdstrDevCert, DRM_DEVCERT_DEVICEPUBKEY, NULL, &dstrPubkey));
        ChkDR(DRM_UTL_StringRemoveSubString(f_pdstrDevCert, &dstrPubkey));        
        ChkDR(DRM_UTL_StringInsertBlankSubString(
                f_pdstrDevCert,
                (DRM_DWORD)(dstrPubkey.pwszString - f_pdstrDevCert->pwszString), 
                CCH_BASE64_EQUIV(PK_ENC_PUBLIC_KEY_LEN)));

        dstrPubkey.cchString = CCH_BASE64_EQUIV(PK_ENC_PUBLIC_KEY_LEN);

        /* Make room for Device Privkey in DEVCERT/CERTIFICATE type="DEVICE"/DATA/KEYDATA */
        ChkDR(DRM_DCP_GetAttribute((DRM_CONST_STRING*)f_pdstrDevCert, DRM_DEVCERT_DEVICEPRIVKEY, NULL, &dstrPrivkey));
        ChkDR(DRM_UTL_StringRemoveSubString(f_pdstrDevCert, &dstrPrivkey));

        dstrPrivkey.cchString = CCH_BASE64_EQUIV(SIZEOF(PRIVKEY));              
        ChkDR(DRM_UTL_StringInsertBlankSubString(
                f_pdstrDevCert,
                (DRM_DWORD)(dstrPrivkey.pwszString - f_pdstrDevCert->pwszString),
                dstrPrivkey.cchString ) );

        /* generate key pair */    
        ChkDR(DRM_PK_GenKeyPair(f_pcontextCRYP->rgbCryptoContext, (PUBKEY*)(dstrPubkey.pwszString), (PRIVKEY*)(dstrPrivkey.pwszString)));
        
        /* Save Device Pubkey in fallback cert
        */
        {
            PKCERT cert={0};
            DRM_CONST_STRING dstrSecurityVersion = EMPTY_DRM_STRING;

            /* locate FALLBACK/SECURITYVERSION */
            ChkDR(DRM_XML_GetSubNode((DRM_CONST_STRING*)f_pdstrDevCert, &g_dstrFallBack, NULL, NULL, 0, NULL, &dstrCertNodeData, 1));
            ChkDR(DRM_XML_GetNode( 
                    &dstrCertNodeData, 
                    &g_dstrTagDataSecVer, 
                    NULL, 
                    NULL,
                    0, 
                    NULL, 
                    &dstrInsert));
            {
                DRM_WORD rgwVersion[VERSION_LEN];
                DRM_INT  i = 0;
                ChkDR( DRM_UTL_GetVersionFromString( dstrInsert.pwszString, dstrInsert.cchString, rgwVersion ) );
                for( i=0; i < VERSION_LEN; i++ )
                {
                    PUT_BYTE(cert.pk.version, i, (DRM_BYTE)rgwVersion[i] );
                }
            }

            /* Make room and sign the data node with GC Privkey */
            ChkDR(DRM_XML_GetSubNode( 
                    (DRM_CONST_STRING*)f_pdstrDevCert, 
                    &g_dstrFallBack, 
                    NULL, 
                    NULL, 
                    0, 
                    NULL, 
                    &dstrCertNodeData, 
                    1));

            ChkDR(DRM_XML_GetNode( 
                    &dstrCertNodeData, 
                    &g_dstrTagCertificate, 
                    &g_dstrRootSigValueVersionTag, 
                    &g_dstrRootSigValueVersionVal, 
                    0, 
                    NULL, 
                    &dstrInsert));

            ChkDR(DRM_UTL_StringRemoveSubString(f_pdstrDevCert, &dstrInsert));
            ChkDR(DRM_UTL_StringInsertBlankSubString(
                    f_pdstrDevCert,
                    (DRM_DWORD)(dstrInsert.pwszString - f_pdstrDevCert->pwszString),
                    CCH_BASE64_EQUIV(SIZEOF(PKCERT))));
            dstrInsert.cchString = CCH_BASE64_EQUIV(PK_ENC_SIGNATURE_LEN);

            MEMCPY(&cert.pk.pk, PB_DSTR(&dstrPubkey), SIZEOF(PUBKEY));
            ChkDR(OEM_GetFallbackSigningKey(&FBSigningKey));

            ChkDR(DRM_PK_Sign( 
                    f_pcontextCRYP->rgbCryptoContext, 
                    &FBSigningKey,
                    (DRM_BYTE *)&cert.pk, 
                    SIZEOF(PK), 
                    cert.sign));
            ZEROMEM((DRM_BYTE*)&FBSigningKey, SIZEOF(PRIVKEY));

            MEMCPY( PB_DSTR(&dstrInsert), &cert, SIZEOF(PKCERT));

            /* B64 encode in place */
            dstrInsert.cchString = CCH_BASE64_EQUIV(SIZEOF(PKCERT));
            ChkDR(DRM_B64_EncodeW( 
                    PB_DSTR(&dstrInsert), 
                    SIZEOF(PKCERT), 
                    (DRM_WCHAR*)dstrInsert.pwszString, 
                    &dstrInsert.cchString, 
                    0));
        }

        /* B64 encode pubkey in place */
        ChkDR(DRM_B64_EncodeW( 
                PB_DSTR(&dstrPubkey), 
                SIZEOF(PUBKEY), 
                (DRM_WCHAR*)dstrPubkey.pwszString,
                &dstrPubkey.cchString,
                0) );
        
        ChkDR(OEM_GetGroupCertPrivateKey(&GCPrivKey));
        ChkDR( DRM_PK_SymmetricCrypt( (DRM_BYTE*)&GCPrivKey, 
                                      SIZEOF(PRIVKEY),
                                      SIZEOF(PRIVKEY),
                                      PB_DSTR(&dstrPrivkey), 
                                      0,
                                      NULL ) );

        ChkDR(DRM_B64_EncodeW(
                PB_DSTR(&dstrPrivkey), 
                SIZEOF(PRIVKEY),
                (DRM_WCHAR*)dstrPrivkey.pwszString, 
                &dstrPrivkey.cchString, 
                0));        

        /* Make room and sign the data node with GC Privkey */
        ChkDR(DRM_XML_GetSubNode( 
                (DRM_CONST_STRING*)f_pdstrDevCert, 
                &g_dstrTagCertificate, 
                &g_dstrAttributeType, 
                &g_dstrTagDevice, 
                0, 
                NULL, 
                &dstrCertNodeData, 
                1));
        
        ChkDR(DRM_XML_GetNode( 
                &dstrCertNodeData, 
                &g_dstrTagMSDRMSignature, 
                NULL, 
                NULL, 
                0, 
                NULL, 
                &dstrInsert));

        ChkDR(DRM_UTL_StringRemoveSubString(f_pdstrDevCert, &dstrInsert));
        ChkDR(DRM_UTL_StringInsertBlankSubString(
                f_pdstrDevCert,
                (DRM_DWORD)(dstrInsert.pwszString - f_pdstrDevCert->pwszString), 
                CCH_BASE64_EQUIV(PK_ENC_SIGNATURE_LEN)));
        dstrInsert.cchString = CCH_BASE64_EQUIV(PK_ENC_SIGNATURE_LEN);

        /* sign the devcert with GC privkey */
        ChkDR(DRM_XML_GetNode(&dstrCertNodeData, &g_dstrTagData, NULL, NULL, 0, &dstrDataNodeData, NULL));

        ChkDR(DRM_PK_Sign( 
                f_pcontextCRYP->rgbCryptoContext,
                &GCPrivKey,
                PB_DSTR(&dstrDataNodeData),
                CB_DSTR(&dstrDataNodeData),
                PB_DSTR(&dstrInsert)));        

        /* B64 encode the signature in place */
        ChkDR(DRM_B64_EncodeW( 
                PB_DSTR(&dstrInsert), 
                PK_ENC_SIGNATURE_LEN, 
                (DRM_WCHAR*)dstrInsert.pwszString, 
                &dstrInsert.cchString, 
                0));

#if DRM_SUPPORT_SYMMETRIC_OPTIMIZATIONS
        /*
        ** Create the symmetric signature for the device to use 
        */
        ChkDR(DRM_XML_GetSubNode( (DRM_CONST_STRING*)f_pdstrDevCert, 
                                  &g_dstrTagCertificate, 
                                  &g_dstrAttributeType, 
                                  &g_dstrTagDevice, 
                                   0, 
                                   NULL, 
                                  &dstrCertNodeData, 
                                  1));

        ChkDR(DRM_XML_GetNode( &dstrCertNodeData,
                               &g_dstrTagSymSig, 
                                NULL, 
                                NULL, 
                                0, 
                                NULL, 
                               &dstrInsert));

        ChkDR(DRM_UTL_StringRemoveSubString(f_pdstrDevCert, &dstrInsert));
        ChkDR(DRM_UTL_StringInsertBlankSubString( f_pdstrDevCert,
                                      (DRM_DWORD)(dstrInsert.pwszString - f_pdstrDevCert->pwszString), 
                                                 CCH_BASE64_EQUIV(SHA_DIGEST_LEN) ) );
        
        dstrInsert.cchString = CCH_BASE64_EQUIV(SHA_DIGEST_LEN);

        ChkDR(DRM_PK_SymmetricSign( (DRM_BYTE*)&GCPrivKey,
                                    SIZEOF( GCPrivKey ),
                                    PB_DSTR(&dstrDataNodeData),
                                    CB_DSTR(&dstrDataNodeData),
                                    PB_DSTR(&dstrInsert) ) );

        /* B64 encode the signature in place */
        ChkDR(DRM_B64_EncodeW( PB_DSTR(&dstrInsert), 
                               SHA_DIGEST_LEN, 
                   (DRM_WCHAR*)dstrInsert.pwszString, 
                              &dstrInsert.cchString, 
                               0));
#endif
    }

    /* save the devcert on device */
    ChkDR( OEM_SetDeviceCert( 
            PB_DSTR( f_pdstrDevCert ),
            CB_DSTR( f_pdstrDevCert ) ) );
        
ErrorExit:    
    DRM_PROFILING_LEAVE_SCOPE(L"DRM_DDC_GetDeviceCertificate", g_pwszLeavingFunction);

    ZEROMEM(&GCPrivKey,    SIZEOF(PRIVKEY));
    ZEROMEM(&FBSigningKey, SIZEOF(PRIVKEY));

    return dr;
}

