def __iter__(self): re = r sha256 = hashlib.sha256() counter = 0 last_counter = 0 dgst = None while dgst != digest: for chunk in re.iter_content(chunk_size): sha256.update(chunk) counter += 1 yield chunk dgst = 'sha256:' + sha256.hexdigest() if dgst != digest: if counter == last_counter: raise exceptions.DXFDigestMismatchError( dgst, digest) last_counter = counter re = dxf_instance._request( 'get', 'blobs/' + digest, stream=True, headers={ "Range": "bytes={}-".format((counter + 1) * chunk_size) })
def get_alias(self, alias=None, manifest=None, verify=True, sizes=False): """ Get the blob hashes assigned to an alias. :param alias: Alias name. You almost definitely will only need to pass this argument. :type alias: str :param manifest: If you previously obtained a manifest, specify it here instead of ``alias``. You almost definitely won't need to do this. :type manifest: str :param verify: (v1 schema only) Whether to verify the integrity of the alias definition in the registry itself. You almost definitely won't need to change this from the default (``True``). :type verify: bool :param sizes: Whether to return sizes of the blobs along with their hashes :type sizes: bool :rtype: list :returns: If ``sizes`` is falsey, a list of blob hashes (strings) which are assigned to the alias. If ``sizes`` is truthy, a list of (hash,size) tuples for each blob. """ if alias: r = self._request('get', 'manifests/' + alias, headers={ 'Accept': _schema2_mimetype + ', ' + _schema1_mimetype }) manifest = r.content.decode('utf-8') dcd = r.headers['docker-content-digest'] else: dcd = None parsed_manifest = json.loads(manifest) if parsed_manifest['schemaVersion'] == 1: dgsts = _verify_manifest(manifest, parsed_manifest, dcd, verify) if not sizes: return dgsts return [(dgst, self.blob_size(dgst)) for dgst in dgsts] else: if dcd: method, expected_dgst = dcd.split(':') if method != 'sha256': raise exceptions.DXFUnexpectedDigestMethodError( method, 'sha256') hasher = hashlib.new(method) hasher.update(r.content) dgst = hasher.hexdigest() if dgst != expected_dgst: raise exceptions.DXFDigestMismatchError( dgst, expected_dgst) r = [] for layer in parsed_manifest['layers']: method, dgst = layer['digest'].split(':') if method != 'sha256': raise exceptions.DXFUnexpectedDigestMethodError( method, 'sha256') r.append((dgst, layer['size']) if sizes else dgst) return r
def __iter__(self): sha256 = hashlib.sha256() for chunk in r.iter_content(chunk_size): sha256.update(chunk) yield chunk dgst = 'sha256:' + sha256.hexdigest() if dgst != digest: raise exceptions.DXFDigestMismatchError(dgst, digest)
def _get_alias(self, alias, manifest, verify, sizes, dcd, get_digest): # pylint: disable=too-many-arguments if alias: manifest, r = self.get_manifest_and_response(alias) dcd = r.headers['docker-content-digest'] parsed_manifest = json.loads(manifest) if parsed_manifest['schemaVersion'] == 1: # https://github.com/docker/distribution/issues/1662#issuecomment-213101772 # "A schema1 manifest should always produce the same image id but # defining the steps to produce directly from the manifest is not # straight forward." if get_digest: raise exceptions.DXFDigestNotAvailableForSchema1() r = _verify_manifest(manifest, parsed_manifest, dcd, verify) return [(dgst, self.blob_size(dgst)) for dgst in r] if sizes else r if dcd: method, expected_dgst = split_digest(dcd) hasher = hashlib.new(method) hasher.update(r.content) dgst = hasher.hexdigest() if dgst != expected_dgst: raise exceptions.DXFDigestMismatchError( method + ':' + dgst, method + ':' + expected_dgst) if get_digest: dgst = parsed_manifest['config']['digest'] split_digest(dgst) return dgst r = [] for layer in parsed_manifest['layers']: dgst = layer['digest'] split_digest(dgst) r.append((dgst, layer['size']) if sizes else dgst) return r
def _verify_manifest(content, manifest, content_digest=None, verify=True): # pylint: disable=too-many-locals,too-many-branches # Adapted from https://github.com/joyent/node-docker-registry-client if verify or ('signatures' in manifest): signatures = [] for sig in manifest['signatures']: protected64 = sig['protected'] protected = _urlsafe_b64decode(protected64).decode('utf-8') protected_header = json.loads(protected) format_length = protected_header['formatLength'] format_tail64 = protected_header['formatTail'] format_tail = _urlsafe_b64decode(format_tail64).decode('utf-8') alg = sig['header']['alg'] if alg.lower() == 'none': raise exceptions.DXFDisallowedSignatureAlgorithmError('none') if sig['header'].get('chain'): raise exceptions.DXFSignatureChainNotImplementedError() signatures.append({ 'alg': alg, 'signature': sig['signature'], 'protected64': protected64, 'key': _import_key(sig['header']['jwk']), 'format_length': format_length, 'format_tail': format_tail }) payload = content[:signatures[0]['format_length']] + \ signatures[0]['format_tail'] payload64 = _urlsafe_b64encode(payload) else: payload = content if content_digest: method, expected_dgst = split_digest(content_digest) hasher = hashlib.new(method) hasher.update(payload.encode('utf-8')) dgst = hasher.hexdigest() if dgst != expected_dgst: raise exceptions.DXFDigestMismatchError( method + ':' + dgst, method + ':' + expected_dgst) if verify: for sig in signatures: jwstoken = jws.JWS() jwstoken.deserialize( json.dumps({ 'payload': payload64, 'protected': sig['protected64'], 'signature': sig['signature'] }), sig['key'], sig['alg']) dgsts = [] for layer in manifest['fsLayers']: dgst = layer['blobSum'] split_digest(dgst) dgsts.append(dgst) return dgsts
def _verify_manifest(content, manifest, content_digest=None, verify=True): # pylint: disable=too-many-locals,too-many-branches # Adapted from https://github.com/joyent/node-docker-registry-client if verify or ('signatures' in manifest): signatures = [] for sig in manifest['signatures']: protected64 = sig['protected'] protected = _urlsafe_b64decode(protected64).decode('utf-8') protected_header = json.loads(protected) format_length = protected_header['formatLength'] format_tail64 = protected_header['formatTail'] format_tail = _urlsafe_b64decode(format_tail64).decode('utf-8') alg = sig['header']['alg'] if alg.lower() == 'none': raise exceptions.DXFDisallowedSignatureAlgorithmError('none') if sig['header'].get('chain'): raise exceptions.DXFSignatureChainNotImplementedError() signatures.append({ 'alg': alg, 'signature': sig['signature'], 'protected64': protected64, 'key': _jwk_to_key(sig['header']['jwk']), 'format_length': format_length, 'format_tail': format_tail }) payload = content[:signatures[0]['format_length']] + \ signatures[0]['format_tail'] payload64 = _urlsafe_b64encode(payload) else: payload = content if content_digest: method, expected_dgst = content_digest.split(':') if method != 'sha256': raise exceptions.DXFUnexpectedDigestMethodError(method, 'sha256') hasher = hashlib.new(method) hasher.update(payload.encode('utf-8')) dgst = hasher.hexdigest() if dgst != expected_dgst: raise exceptions.DXFDigestMismatchError(dgst, expected_dgst) if verify: for sig in signatures: data = {'key': sig['key'], 'header': {'alg': sig['alg']}} jws.header.process(data, 'verify') sig64 = sig['signature'] data['verifier']("%s.%s" % (sig['protected64'], payload64), _urlsafe_b64decode(sig64), sig['key']) dgsts = [] for layer in manifest['fsLayers']: method, dgst = layer['blobSum'].split(':') if method != 'sha256': raise exceptions.DXFUnexpectedDigestMethodError(method, 'sha256') dgsts.append(dgst) return dgsts