In order to get the same output shape BxCxDxD you have to do two things:
to keep DxD: use padding, in your case padding=(1,1). This adds a “border” of zeros around your input, so the center of a 3x3 kernel can visit every point on your DxD grid (looking at your CxDxD stack “top-down” so to say).
to keep the number of channels C the same: use C kernels, the number of channels of the output of conv2d will always be the number of kernels used.
adding such a layer to a model in pytorch would look like this:
self.conv = nn.Conv2d(in_channels=C, out_channels=C, kernel_size=(3, 3), padding=(1, 1))