Skip to content
This repository has been archived by the owner on Nov 5, 2019. It is now read-only.

Helpful error message in P12 factory in absence of pyOpenSSL. #424

Merged
merged 1 commit into from
Feb 20, 2016

Conversation

dhermes
Copy link
Contributor

@dhermes dhermes commented Feb 20, 2016

Fixes #417.

@nathanielmanistaatgoogle I'm not sure why this didn't make it in the re-write. I definitely had tests for this and an if crypt.Signer check.

Sorry @ddoskind that I absent-mindedly let it slip in #398 (it was in #392 but I dropped the _get_signer helper since only the factories need to make a signer now rather than the constructor back then)

@nathanielmanistaatgoogle
Copy link
Contributor

Looks good; merge when you please.

dhermes added a commit that referenced this pull request Feb 20, 2016
Helpful error message in P12 factory in absence of pyOpenSSL.
@dhermes dhermes merged commit 64e593e into googleapis:master Feb 20, 2016
@dhermes dhermes deleted the fix-417 branch February 20, 2016 01:19
@dhermes dhermes mentioned this pull request Mar 3, 2016
@@ -240,6 +240,8 @@ def _from_p12_keyfile_contents(cls, service_account_email,
"""
if private_key_password is None:
private_key_password = _PASSWORD_DEFAULT
if crypt.Signer is not crypt.OpenSSLSigner:

This comment was marked as spam.

This comment was marked as spam.

@codingjoe
Copy link

Hi,

this seems to have introduced a bug. You can no longer use any other signer as the OpenSSL one.

This seems strange, is that intended behavior?

Cheers,
Joe

@dhermes
Copy link
Contributor Author

dhermes commented Mar 7, 2016

The rsa library (and the ASN.1 helpers) were never able to parse P12 keys, but they work just fine with JSON keys.

  1. Did you actually have them working with P12?
  2. Do you have a need for P12 keys instead of JSON?

@codingjoe
Copy link

I use Django on Heroku. On Heroku you can't store files except in your repository. Adding a private key to the repository is not the best idea I guess ;)
That's why I added it to the environment.

I wrote that little bit of code that worked for me until this change:

def build_googleapiclient(service_email=None, private_key=None,
                          user_email=None):
    service_email = service_email or settings.GOOGLE_DRIVE_STORAGE_SERVICE_EMAIL
    key = private_key or settings.GOOGLE_DRIVE_STORAGE_KEY

    credentials = ServiceAccountCredentials._from_p12_keyfile_contents(
        service_email,
        key,
        scopes="https://www.googleapis.com/auth/drive",
    )
    credentials = credentials.create_delegated(user_email)
    http = httplib2.Http()
    http = credentials.authorize(http)

    return build('drive', 'v2', http=http)

I know it's not nice to use a private API and than complain, but hey ;)

Since my private key start with -----BEGIN RSA PRIVATE KEY----- I naturally assumed it's an RSA key and not P12.

@dhermes
Copy link
Contributor Author

dhermes commented Mar 8, 2016

-----BEGIN RSA PRIVATE KEY----- is not a P12 key, it is likely a PCKS#1 key in PEM format (and if it's from Google you probably got it by converting a P12 key to it).

rsa and crypt.RsaSigner are just libraries, they don't (necessarily) relate to the key type. Any of the three possible signers will work with that key.

I'd first and foremost just recommend getting a JSON key file from Google, since it's unclear where you got the PKCS#1 key from. (A JSON keyfile is a JSON-ified dict, with a field that holds a PKCS#8 key in PEM format.)

If you want to proceed "as-is" by just constructing any old signer:

signer = crypt.Signer.from_string(settings.GOOGLE_DRIVE_STORAGE_KEY)
creds = ServiceAccountCredentials(
    settings.GOOGLE_DRIVE_STORAGE_SERVICE_EMAIL, signer,
    scopes='https://www.googleapis.com/auth/drive',
    sub=user_email)
# OPTIONAL HACKY SNIPPET FOR SERIALIZATION
creds._private_key_pkcs8_pem = settings.GOOGLE_DRIVE_STORAGE_KEY 
# DEFINITELY A LIE SINCE YOUR STORAGE KEY IS PKCS#1, NOT PKCS#8

Can you give an idea / snippet of what you were doing before (with SignedJwtAssertionCredentials, before the 2.0.0 release)?

@codingjoe
Copy link

@dhermes: sure, reversion says we used this one :)

def build_googleapiclient(service_email=None, private_key=None,
                          user_email=None):
    service_email = service_email or settings.GOOGLE_DRIVE_STORAGE_SERVICE_EMAIL
    key = private_key or settings.GOOGLE_DRIVE_STORAGE_KEY

    kwargs = {}
    if user_email:
        kwargs['sub'] = user_email
    credentials = SignedJwtAssertionCredentials(
        service_email,
        key,
        scope="https://www.googleapis.com/auth/drive",
        **kwargs
    )
    http = httplib2.Http()
    http = credentials.authorize(http)

    return build('drive', 'v2', http=http)

Is the JSON key file available now for server2server communication? We needed a user that can modify out Drive regardless of the current user.

@dhermes
Copy link
Contributor Author

dhermes commented Mar 9, 2016

The key you have is fine. Yes a JSON keyfile is a keyfile for a Google service account.

The snippets I mentioned above will work for you (I'm 99.9% sure your PKCS#1 key is just converted from an original P12 key downloaded from Google).

@codingjoe
Copy link

@dhermes thanks! I just saw that you can now create json files for service accounts. I just created new creds for the account in json:

def build_googleapiclient(user_email=None):
    key = json.loads(settings.GOOGLE_DRIVE_STORAGE_KEYP)

    credentials = ServiceAccountCredentials._from_parsed_json_keyfile(
        key,
        scopes="https://www.googleapis.com/auth/drive",
    )
    if user_email is not None:
        credentials = credentials.create_delegated(user_email)
    http = httplib2.Http()
    http = credentials.authorize(http)

    return build('drive', 'v2', http=http)

This works fine for me. Forget the rest ;)

@dhermes
Copy link
Contributor Author

dhermes commented Mar 10, 2016

Good deal! I strongly encourage you just use

ServiceAccountCredentials.from_json_keyfile_name(
    settings.GOOGLE_DRIVE_STORAGE_KEYP,
    scopes='https://www.googleapis.com/auth/drive')

instead of using a non-public interface.

@codingjoe
Copy link

@dhermes that is a nice Idea, but again, I can't store files on Heroku that are not in the git repository.
You're not encouraging me to put a PK in my repo, are you ;)

@dhermes
Copy link
Contributor Author

dhermes commented Mar 11, 2016

Sorry I misread the json.loads as json.load. I wasn't thinking about your environment.

Still, you don't need the non-public method. Use http://oauth2client.readthedocs.org/en/latest/source/oauth2client.service_account.html#oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_dict

@codingjoe
Copy link

Good point, thx!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants