Hello - I'm having some trouble updating S/MIME for a user via the .NET SDK for Gmail, every attempt results in a 400 Bad Request response. I'm attempting to implement this from PowerShell as part of my module, PSGSuite, but have not had much luck with this particular API. Issue tracking this in that repo: https://github.com/scrthq/PSGSuite/issues/57
Checking through the documentation and source code for various SDK languages as well as the REST API itself isn't too helpful, as it appears the .NET SDK is the only language that is expecting a String type for the Pkcs12 property on the SmimeInfo body object, everything else expects the certificate bytes. Attempting to pass in the byte array works, but implicitly casts it to a single string, which results in a 400 Bad Request. I've also attempted to convert to both Base64 and URLSafeBase64, those fail with the same 400 error.
Should the Pkcs12 property on the SMimeInfo object be expecting a Byte[] type instead of String? Is there something I'm doing wrong here?

I believe it's being generated correctly, and that if you use base64 it should be okay. It's not clear to me what you mean by "attempting to pass in the byte array works" - it would be really helpful if you could provide a complete example showing what you're doing, along with details of your environment.
Note that everyone in the team is on vacation until January 2nd, but we'll look into this when we can - the more information you can provide to help us reproduce the issue, the better.
Hey @jskeet - Thank you for the quick response! No worries on the follow-up delay, this has been something I've been trying to solve for a while now. The attempts from my end have all been from PowerShell, which is working fine for the numerous other Google SDKs, including the other sections of the Gmail .NET Client. This, so far, has been the only request type that I haven't been able to get past. It may take me a little longer to get a C# example, so please let me know if PowerShell is fine given the provided information.
What I meant by "attempting to pass in the byte array works" is that it allows it, as it implicitly casts the byte array to a string instead of throwing a type error.
Gmail .NET Client version - Still happening in the latest release (1.42.0.1777), but has also been happening for a while with no change (my code initially developed as of Jul 5, 2018, so as early as version 1.34.0.1223 of the Gmail .NET Client)
Target Frameworks tested:
Example input variables for each scenario:
$User = '[email protected]'
$SendAsEmail = '[email protected]'
$Pkcs12Path = 'C:\GSuite\SMimeKey.p12'
$service object information:
# TypeName: Google.Apis.Gmail.v1.GmailService
> $service
Features : {}
Name : gmail
BaseUri : https://www.googleapis.com/gmail/v1/users/
BasePath : gmail/v1/users/
BatchUri : https://www.googleapis.com/batch/gmail/v1
BatchPath : batch/gmail/v1
Users : Google.Apis.Gmail.v1.UsersResource
HttpClient : Google.Apis.Http.ConfigurableHttpClient
HttpClientInitializer : Google.Apis.Auth.OAuth2.ServiceAccountCredential
GZipEnabled : True
ApiKey :
ApplicationName : PSGSuite
Serializer : Google.Apis.Json.NewtonsoftJsonSerializer
Attempt 1 - Read in the certificate as a byte array
$pkcs12Content = [System.IO.File]::ReadAllBytes($Pkcs12Path)
$body = [Google.Apis.Gmail.v1.Data.SmimeInfo]::new()
$body.Pkcs12 = $pkcs12Content
$request = $service.Users.Settings.SendAs.SmimeInfo.Insert($body,$User,$SendAsEmail)
$request.Execute()
Attempt 2 - Convert certificate bytes to Base64
$pkcs12Content = [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($Pkcs12Path))
$body = [Google.Apis.Gmail.v1.Data.SmimeInfo]::new()
$body.Pkcs12 = $pkcs12Content
$request = $service.Users.Settings.SendAs.SmimeInfo.Insert($body,$User,$SendAsEmail)
$request.Execute()
Attempt 3 - Convert Base64 to URLSafeBase64 using String methods
$pkcs12Content = [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($Pkcs12Path))
$urlSafePkcs12Content = $pkcs12Content.TrimEnd("=").Replace('+', '-').Replace('/', '_')
$body = [Google.Apis.Gmail.v1.Data.SmimeInfo]::new()
$body.Pkcs12 = $urlSafePkcs12Content
$request = $service.Users.Settings.SendAs.SmimeInfo.Insert($body,$User,$SendAsEmail)
$request.Execute()
Generic error that's being returned when trying to execute the Insert() request to insert a new SmimeInfo for all of the attempts above:
Exception calling "Execute" with "0" argument(s): "Google.Apis.Requests.RequestError
Bad Request [400]
Errors [
Message[Bad Request] Location[ - ] Reason[invalidArgument] Domain[global]
]
Please let me know if there's any additional info I can provide! I appreciate the help!
Attempt 2 is what I'd expect to work. Will see what I can do when I get some time.
Thanks, @jskeet! I appreciate you digging into it! Enjoy your time off, no rush on this!
I was doing some additional digging around other libraries that support adding S/MIME certificates currently and noticed that GAM (Python based) is effectively doing Attempt 3, but the native base64.urlsafe_b64encode() method is _not_ trimming the trailing padding characters.
Going to try testing with a modified Attempt 3 once I can get a tester available (I don't have Enterprise personally to test with 馃檨 )
Testing the below and comparing the output with what GAM is doing in Python is yielding the exact same string, so it seems like this might be a winner to me.
$pkcs12Content = [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($Pkcs12Path))
$urlSafePkcs12Content = $pkcs12Content.Replace('+', '-').Replace('/', '_')
$body = [Google.Apis.Gmail.v1.Data.SmimeInfo]::new()
$body.Pkcs12 = $urlSafePkcs12Content
$request = $service.Users.Settings.SendAs.SmimeInfo.Insert($body,$User,$SendAsEmail)
$request.Execute()
It's odd that you'd need to use a URL-safe base64 encoding. It's not on a URL, so I wouldn't expect to need it. Let me know how you get on.
Agreed, very odd! I'll let you know as soon as I have some test results, but it may be a little bit since I'm dependent on others with G Suite Enterprise to test the updated code.
Once we figure out the underlying issue, could we possible update the documentation for better clarity on what exactly this property expects, format-wise?
Maybe - all the reference documentation is auto-generated, so I'll need to work out how to phrase it properly in a cross-platform way.
Fair!
Right, I've now consulted with a colleague, and we agree that the URL-safe encoding (without trimming) is likely to work. That would match other behavior in the gmail API which is documented, e.g. here for the "raw" field: https://developers.google.com/gmail/api/v1/reference/users/messages#resource
If you could confirm that it works for you when you're able to do so, we can try to get the documentation amended to make it clear using the same wording as for Message.raw. Thanks!
Makes sense! As soon as I hear back, I'll let you know here! I'll try and do some testing this weekend as well once I have some time to work on it after bumping my account up to Enterprise temporarily.
Closing as I assume it works now. Please add a comment if you're still having problems.