Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added HMAC-SHA256 signature method in OAuthHmacSha256Signer #703

Closed
wants to merge 22 commits into from
Closed

feat: Added HMAC-SHA256 signature method in OAuthHmacSha256Signer #703

wants to merge 22 commits into from

Conversation

svonduhn
Copy link
Contributor

@svonduhn svonduhn commented Jun 18, 2021

I have added OAuthHmacSha256Signer to enable using the signature method to HMAC-SHA256.

Unit test/code coverage has been added in OAuthHmacSha256SignerTest.

This change is necessary for establishing connections to NetSuite as they will no longer accept HMAC-SHA1 as a signature method.

Fixes #702 ☕️

This pull request should be close and replaced by #711. There were issues with the original commit email, and I was unable to resolve them by rebasing (a few of the commits did not get rebased correctly).

@svonduhn svonduhn requested a review from a team as a code owner June 18, 2021 20:10
@google-cla google-cla bot added the cla: no This human has *not* signed the Contributor License Agreement. label Jun 18, 2021
@svonduhn svonduhn changed the title Added support for changing the signature method in the OAuthHmacSigner feat: Added support for changing the signature method in the OAuthHmacSigner Jun 18, 2021
@@ -32,14 +34,34 @@
@Beta
public final class OAuthHmacSigner implements OAuthSigner {

private final Map<String, String> signatureMethodMap = ImmutableMap.of(
"HMAC-SHA1", "HmacSHA1",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why you need both the keys and the values here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. The signature method that need to be used for the OAuth 1.0 header has to be of the format: "HMAC-SHA1" or "HMAC-SHA2", while the actual Algorithm strings that Java requires have to be of the format "HmacSHA1" or "HmacSHA256" as described here: https://docs.oracle.com/javase/9/docs/specs/security/standard-names.html#mac-algorithms.

My intent was to use a Map to associate the signature method as the key with the corresponding algorithm as the value, so there would not be an accidental mismatch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has been reverted back in favor of a new class


/** Set the signature method. Valid signature methods are "HMAC-SHA1" and "HMAC-SHA256" */
public void setSignatureMethod(String signatureMethod) throws IllegalArgumentException {
if (signatureMethodMap.containsKey(signatureMethod)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use an enum instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has been reverted back in favor of a new class

@@ -32,4 +35,39 @@ public void testComputeSignature() throws GeneralSecurityException {
signer.tokenSharedSecret = "def";
assertEquals(EXPECTED_SIGNATURE, signer.computeSignature("foo"));
}

public void testComputeSignatureHmacSha256() throws GeneralSecurityException {
final OAuthHmacSigner signer = new OAuthHmacSigner();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Google style specifies no final on local variables except where required

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has been reverted back in favor of a new class


public void testSetSignatureMethodHmacMD5() {
assertThrows(
"Signature method HMAC-MD5 not available",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

four space indents for continuation lines, per Google style guide

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has been reverted back in favor of a new class

@elharo elharo requested a review from silvolu June 18, 2021 20:35
Copy link
Contributor

@elharo elharo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps HMAC-SHA256 should be a separate class? I'm not sure.

@@ -32,14 +34,34 @@
@Beta
public final class OAuthHmacSigner implements OAuthSigner {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

class doc comment needs to be updated

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has been reverted back in favor of a new class

@svonduhn
Copy link
Contributor Author

perhaps HMAC-SHA256 should be a separate class? I'm not sure.

I think you may be right about having a separate class. I was trying not to repeat code in a class that would be similar in every way except for three lines, but having a separate class makes a lot of sense, particularly since this is likely to be a special use case signing class, only needed for a few services. I am going to refactor this as a separate class and see if that is simpler.

@svonduhn svonduhn changed the title feat: Added support for changing the signature method in the OAuthHmacSigner feat: Added HMAC-SHA256 signature method in OAuthHmacSha256Signer Jun 19, 2021
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2010 Google Inc.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs current copyright date, Inc. --> LLC

and never, ever use (c) in anything, here or elsewhere. It is not recognized by the law, no matter how many places you find it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has been updated

* {@link Beta} <br>
* OAuth {@code "HMAC-SHA256"} signature method.
*/
@Beta
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auth leader (not me) needs to figure out if we want this and if it should be beta

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the interface the OAuthSigner interface itself is Beta I took that as a signal that any classes implementing it should also be Beta.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way annotation might be enough. Is there a reason to include this in doc comment too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed this from the doc comment

public final class OAuthHmacSha256Signer implements OAuthSigner {

/** Client-shared secret or {@code null} for none. */
public String clientSharedSecret;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private on fields please

Copy link
Contributor Author

@svonduhn svonduhn Jun 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can make these fields private, but then will also need to add accessors/mutators. I noticed in the original class (OAuthHmacSigner) these fields were public and had no methods to get/set the values. Do we want to have this signing class implemented differently from the other on that it is based on? Existing code using OAuthHmacSigner that needs to migrate to this class would have fewer code changes keeping it the same, but then the API was labelled Beta.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mutable public fields are a hard no. Don't try to be consistent with bad code. You can probably make these final with no mutators.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client secret and the token secret probably shouldn't be final, as there is often a multi-step flow to obtain the values in separate steps, so they need to be set after the signer instance is created in most instances.

Also, in looking at the other code required for making a call, the OAuthParameters class will still have mutable public fields (similar to OAuthHmacSigner), and it is necessary to use that class together with the signer, to make a complete OAuth 1.0 signed request:

Current code needed for HMAC-SHA1:

OAuthHmacSigner hmacSha1Signer = new OAuthHmacSigner();
hmacSha1Signer.clientSharedSecret = CLIENT_SECRET;
hmacSha1Signer.tokenSharedSecret = TOKEN_SECRET;
OAuthParameters oauthParameters = new OAuthParameters();
oauthParameters.signer = hmacSha1Signer;
oauthParameters.consumerKey = CONSUMER_KEY;
oauthParameters.token = ACCESS_TOKEN;
HttpRequestFactory requestFactory = new NetHttpTransport().createRequestFactory(oauthParameters);

Making the fields on the new class private with setters would make the code look like this:

New code needed for HMAC-SHA256:

OAuthHmacSha256Signer hmacSha256Signer = new OAuthHmacSha256Signer();
hmacSha256Signer.setClientSecret(CLIENT_SECRET);
hmacSha256Signer.setTokenSecret(TOKEN_SECRET);
OAuthParameters oauthParameters = new OAuthParameters();
oauthParameters.signer = hmacSha256Signer;
oauthParameters.consumerKey = CONSUMER_KEY;
oauthParameters.token = ACCESS_TOKEN;
HttpRequestFactory requestFactory = new NetHttpTransport().createRequestFactory(oauthParameters);

It feels a little bit inconsistent this way, even though this is arguably the better way. Should anything be done about OAuthParameters ? I'd normally not change something if it isn't necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made the fields private, with setter methods.

public String clientSharedSecret;

/** Token-shared secret or {@code null} for none. */
public String tokenSharedSecret;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto, also make final

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made the field private, but not final, due to OAuth 1.0 frequently setting these values in multiple steps of the flow, so they cannot always be set during construction.


public String computeSignature(String signatureBaseString) throws GeneralSecurityException {
// compute key
StringBuilder keyBuf = new StringBuilder();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keyBuffer

Google style never abbreviates

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has been updated

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2012 Google Inc.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix copyright

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has been updated

package com.google.api.client.auth.oauth;

import java.security.GeneralSecurityException;
import junit.framework.TestCase;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

org.junit preferred

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The POM file only has the current junit dependency (junit:junit:4.13.2). I can switch to a different junit, but that would require adding a new dependency. Which group/artifact/version org.junit package is the preferred one to add and use?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

junit.* is the old legacy classes. org.junit are the new ones from JUnit 4.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, I misunderstood. I updated the test to use org.junit / JUnit4

public final class OAuthHmacSha256Signer implements OAuthSigner {

/** Client-shared secret or {@code null} for none. */
public String clientSharedSecret;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mutable public fields are a hard no. Don't try to be consistent with bad code. You can probably make these final with no mutators.

package com.google.api.client.auth.oauth;

import java.security.GeneralSecurityException;
import junit.framework.TestCase;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

junit.* is the old legacy classes. org.junit are the new ones from JUnit 4.

* {@link Beta} <br>
* OAuth {@code "HMAC-SHA256"} signature method.
*/
@Beta
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way annotation might be enough. Is there a reason to include this in doc comment too?

tokenSharedSecret = tokenSecret;
}

public String getSignatureMethod() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Override

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been updated. Strangely, the annotation did not exist in the original implementing class.

return "HMAC-SHA256";
}

public String computeSignature(String signatureBaseString) throws GeneralSecurityException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Override

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been updated. Strangely, the annotation did not exist in the original implementing class.

/** Token secret */
private String tokenSharedSecret;

public void setClientSecret(String clientSecret) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these mutable? It makes the class not thread-safe.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client secret can be final, so I did change it, however the token secret usually is not known until after the first request of the three-legged OAuth 1.0 authorization flow. Because of that, it needs to be able to be set later. This is common for OAuth 1.0, as it is a multi-step process where not all of the values are known prior to the first step.

The token secret will usually be set to the final value only once during the authorization flow, before the second step, and never changed again.

private static final String EXPECTED_SIGNATURE = "xDJIQbKJTwGumZFvSG1V3ctym2tz6kD8fKGWPr5ImPU=";

@Test
public void testComputeSignature() throws GeneralSecurityException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if mutability is deliberate it should be tested.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New tests have been added to cover the null cases

}
keyBuffer.append('&');
String tokenSecret = tokenSharedSecret;
if (tokenSecret != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this branch is not tested when tokenSecret is null

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New tests have been added to cover the cases when tokenSecret is null

// compute key
StringBuilder keyBuffer = new StringBuilder();
String clientSecret = clientSharedSecret;
if (clientSecret != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this branch is not tested when clientSecret is null

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New tests have been added to cover the cases when clientSecret is null

public String computeSignature(String signatureBaseString) throws GeneralSecurityException {
// compute key
StringBuilder keyBuffer = new StringBuilder();
String clientSecret = clientSharedSecret;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why you copy into a local variable here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need, it was simply in the original class that way. I have eliminated the local variable.

keyBuffer.append(OAuthParameters.escape(clientSecret));
}
keyBuffer.append('&');
String tokenSecret = tokenSharedSecret;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why you copy into a local variable here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need, it was simply in the original class that way. I have eliminated the local variable.

private String tokenSharedSecret;

public void setTokenSecret(String tokenSecret) {
tokenSharedSecret = tokenSecret;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably fail if the existing field is not null. Or does it need to be reset? What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a legitimate case for swapping a temporary token for a permanent token. That is a common scenario in OAuth 1.0

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Can you add a test for this?

}

public OAuthHmacSha256Signer(String clientSecret) {
this.clientSharedSecret = clientSecret;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it make sense for this field to be null?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In reality, no, this should never be null. I have worked with other OAuth libraries, though, and none of them actually validate that this is non-null. However, if any code sets this to be null, then the OAuth calls will be completely non-functional, failing to authenticate. Setting this to a valid string is critical to OAuth working at all. None of the other signer classes do any validation of these (in fact, they are just public variables), and we could be more restrictive here, but in practice this will have to be set to a vlid value or the authentication simply won't work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's check for null then? It dies not break anything and adds value of failing early.

keyBuffer.append(OAuthParameters.escape(clientSharedSecret));
}
keyBuffer.append('&');
if (tokenSharedSecret != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's OK for this to be omitted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the token secret can be null, a blank string, or even a temporary token for the first call of the authorization flow. It is intended to be replaced/updated with a permanent token after the initial call.

@TimurSadykov TimurSadykov self-requested a review July 3, 2021 23:55
@TimurSadykov
Copy link
Member

TimurSadykov commented Jul 14, 2021

@svonduhn Please sign the cla, it is required for all the contributors. Here is the instruction: https://opensource.google/docs/cla/#external-contributors

@svonduhn
Copy link
Contributor Author

@svonduhn Please sign the cla, it is required for all the contributors. Here is the instruction: https://opensource.google/docs/cla/#external-contributors

@TimurSadykov I signed the CLA here: https://cla.developers.google.com/clas/edit?id=111692684224848719787&kind=KIND_INDIVIDUAL&domain=DOMAIN_GOOGLE

I also added the Google CLA GitHub App to my GitHub account, but I am not sure what else needs to be done to get the pull request to recognize the CLA. Is there something additional needed?

@svonduhn
Copy link
Contributor Author

@googlebot rescan

@google-cla
Copy link

google-cla bot commented Jul 21, 2021

We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors. If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.
In order to pass this check, please resolve this problem and then comment @googlebot I fixed it.. If the bot doesn't comment, it means it doesn't think anything has changed.

ℹ️ Googlers: Go here for more info.

@TimurSadykov
Copy link
Member

TimurSadykov commented Aug 5, 2021

@svonduhn Hey, sorry for delay. I see what is happening. I can see that your GitHub login has CLA signed. However, it seems like you made a commit using different email, that is not associated with your account: svonduhn@commercehub.com

Please try to add that email to you GitHub account and then rescan.

@google-cla
Copy link

google-cla bot commented Aug 5, 2021

We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors. If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.
In order to pass this check, please resolve this problem and then comment @googlebot I fixed it.. If the bot doesn't comment, it means it doesn't think anything has changed.

ℹ️ Googlers: Go here for more info.

@svonduhn
Copy link
Contributor Author

svonduhn commented Aug 5, 2021

@TimurSadykov you are right, the commits were made with the wrong email address. The only problem I have now is that when I try to add that email address to my account, it says that it is already associated with a different Google account (which it should not be, but that is the error I am getting). Because of that, it seems that I can't actually fix the issue.

Would it be reasonable (acceptable) to simply create a new pull request with these changes using the correct email address, and close this one? My apologies for the trouble. I have updated my .gitconfig to prevent this from happening in the future.

Let me know how best to proceed. Thank you.

@TimurSadykov
Copy link
Member

@svonduhn There are a couple of options:

  1. interactive rebase (get rebase -i master). Don't merge master into the branch before rebase. This way you will rebase all the commits using your account. This should fix CLA.
  2. A new PR as you suggested. The downside here is that @elharo will have harder time to check that all the comments were addressed :) Please provide a link to previous PR in the description of the new one if you go this way.

@google-cla
Copy link

google-cla bot commented Aug 5, 2021

We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors. If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.
In order to pass this check, please resolve this problem and then comment @googlebot I fixed it.. If the bot doesn't comment, it means it doesn't think anything has changed.

ℹ️ Googlers: Go here for more info.

@svonduhn
Copy link
Contributor Author

svonduhn commented Aug 5, 2021

@elharo @TimurSadykov This pull request should be closed and replaced by #711. There were issues with my original commit email, and I was unable to resolve them via an interactive rebase (a few of the commits did not get rebased correctly).

I do apologize for the trouble. The new pull request does appear to be passing the CLA check now. Thank you for your time!

@svonduhn
Copy link
Contributor Author

closing, as this was included in PR #711, which has been merged.

@svonduhn svonduhn closed this Aug 11, 2021
@svonduhn svonduhn deleted the HMAC-SHA256 branch August 11, 2021 22:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: no This human has *not* signed the Contributor License Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

OAuthHmacSigner only supports HMAC-SHA1 (no ability to use HMAC-SHA256 which is required by NetSuite)
4 participants