1# -*- coding: utf-8 -*-
2# Copyright 2015 Google Inc. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""Helper functions for customer-supplied encryption functionality."""
16
17import base64
18import binascii
19from hashlib import sha256
20
21import boto
22
23from gslib.cloud_api import CryptoTuple
24from gslib.exception import CommandException
25
26
27_MAX_DECRYPTION_KEYS = 100
28
29
30def CryptoTupleFromKey(crypto_key):
31  """Returns a CryptoTuple matching the crypto key, or None for no key."""
32  return CryptoTuple(crypto_key) if crypto_key else None
33
34
35def FindMatchingCryptoKey(key_sha256):
36  """Searches .boto config for an encryption key matching the SHA256 hash.
37
38  Args:
39    key_sha256: Base64-encoded string SHA256 hash of the AES256 encryption key.
40
41  Returns:
42    Base64-encoded encryption key string if a match is found, None otherwise.
43  """
44  encryption_key = boto.config.get('GSUtil', 'encryption_key', None)
45  if encryption_key is not None:
46    if key_sha256 == Base64Sha256FromBase64EncryptionKey(encryption_key):
47      return encryption_key
48  for i in range(_MAX_DECRYPTION_KEYS):
49    key_number = i + 1
50    decryption_key = boto.config.get(
51        'GSUtil', 'decryption_key%s' % str(key_number), None)
52    if decryption_key is None:
53      # Reading 100 config values can take ~1ms in testing. To avoid adding
54      # this tax, stop reading keys as soon as we encounter a non-existent
55      # entry (in lexicographic order).
56      break
57    elif key_sha256 == Base64Sha256FromBase64EncryptionKey(decryption_key):
58      return decryption_key
59
60
61def GetEncryptionTuple():
62  """Returns the encryption tuple from .boto configuration."""
63  encryption_key = _GetBase64EncryptionKey()
64  return CryptoTuple(encryption_key) if encryption_key else None
65
66
67def GetEncryptionTupleAndSha256Hash():
68  """Returns encryption tuple and SHA256 key hash from .boto configuration."""
69  encryption_key_sha256 = None
70  encryption_tuple = GetEncryptionTuple()
71  if encryption_tuple:
72    encryption_key_sha256 = Base64Sha256FromBase64EncryptionKey(
73        encryption_tuple.crypto_key)
74  return (encryption_tuple, encryption_key_sha256)
75
76
77def Base64Sha256FromBase64EncryptionKey(encryption_key):
78  return base64.encodestring(binascii.unhexlify(
79      _CalculateSha256FromString(
80          base64.decodestring(encryption_key)))).replace('\n', '')
81
82
83def _CalculateSha256FromString(input_string):
84  sha256_hash = sha256()
85  sha256_hash.update(input_string)
86  return sha256_hash.hexdigest()
87
88
89def _GetBase64EncryptionKey():
90  """Reads the encryption key from .boto configuration.
91
92  Returns:
93    Base64-encoded encryption key string, or None if no encryption key exists
94    in configuration.
95  """
96  encryption_key = boto.config.get('GSUtil', 'encryption_key', None)
97  if encryption_key:
98    # Ensure the key has a valid encoding.
99    try:
100      base64.decodestring(encryption_key)
101    except:
102      raise CommandException(
103          'Configured encryption_key is not a valid base64 string. Please '
104          'double-check your configuration and ensure the key is valid and in '
105          'base64 format.')
106  return encryption_key
107