sexta-feira, março 07, 2008

Python + SSL + Certificado digital

É comum durante o desenvolvimento de um programa que este precise de comunicação com serviços de terceiros disponíveis na rede.

Em Python como em outras linguagens, todo meio de comunicação via rede utiliza basicamente socket em seu nível mais baixo para prover essa comunicação, seja o meio de comunicação SOAP, rpc, http, etc.

Algumas vezes estes serviços externos fornecem a possibilidade de utilização de canais seguros para comunicação (ssl). Em alguns casos o serviço a ser utilizado pode até mesmo obrigar o uso de comunicação segura, para isso precisamos utilizar socket + ssl. Dependendo da necessidade do projeto pode ser necessário utilizar também um certificado digital juntamente com a conexão ssl como forma de autenticação.

Para utilizar comunicação segura o módulo 'socket' prove a função 'ssl', que de maneira simples executa a comunicação com o serviço desejado via ssl. Essa função também pode receber dois parâmetros , 'key_file' e 'cert_file' respectivamente o 'path' do arquivo PEM para a chave privada e o 'path' para o arquivo PEM do certificado.

Uma assinatura digital geralmente é um arquivo binário no formato 'pkcs12' com a extensão '.pfx', sendo assim precisamos abrir esse arquivo e retirar as duas informações que o 'ssl' precisa (key_file, cer_file) e salvar nos arquivos com o formato PEM. Para isso utilizamos openssl e pyopenssl, 'bindings' para python da 'lib' 'openssl'.

Com os módulos instalados, vamos abrir o certificado no formato binário, transformar no objeto 'pkcs12' e então retirar as informações do certificado e da chave privada para em seguida salvar cada um em arquivo separado.

Alguns certificados podem estar codificados sendo necessário informar a frase para decodificação, para tal temos o parâmetro 'passphrase', se não for preciso é só não informar o parâmetro como no exemplo abaixo.


from OpenSSL import crypto

# Lendo o arquivo pfx no formato pkcs12 como binario
pkcs12 = crypto.load_pkcs12(open('certificado.pfx', 'rb').read(), 'passphrase')

# Retorna a string decodificado do certificado
cert_str = crypto.dump_certificate(crypto.FILETYPE_PEM, pkcs12.get_certificate())

# Retorna a string decodificado da chave privada
key_str = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkcs12.get_privatekey())

# Gravando a string no dicso
open('cert.pem', 'wb').write(cert_str)

# Gravando a string no dicso
open('key.ptm', 'wb').write(key_str)


Agora para finalizar basta efetuar a comunicação com o serviço desejado utilizando o modulo 'socket' e a função 'ssl' passando os parâmetros de chave e certificado:


proxy = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
proxy.connect(('www.lzt.com.br', 80))
proxy_ssl = socket.ssl(proxy, 'key.pem', 'cert.pem')


Pronto! Com o objeto 'proxy' podemos fazer requisições a serviços que utilizem conexão 'ssl' com certificados digitais.

terça-feira, fevereiro 27, 2007

Links para o ctypes

Estou dando uma olha no ctypes, vou utilzar aqui na empresa então estou reunindo alguns links sobre o assunto.

Pretendo escrever um tutorial sobre o ctypes, assim que estiver legal posto aqui.

quarta-feira, fevereiro 21, 2007

PyPy - nova versão do projeto

Acaba de sair do forno a versão 0.99.0 do projeto PyPy. IMVHO um dos mais promissores e revolucionários projetos relacionados a Python da atualidade.

Bom, juntei alguns links sobre PyPy. Estou muito impressionado com o projeto e comecei a estudar o modelo de funcionamento dele, assim que tiver algo concreto postarei aqui.

terça-feira, fevereiro 06, 2007

Ordenando listas no python2.3 e inferiores

Ordenação simples

Um objeto to tipo 'list' possui um método chamado 'sort' que faz a ordenação 'in-place' (resultado na própria variável) do objeto.

Descrição do método:

L.sort([cmpfunc]) -- sort *IN PLACE*; if given, cmpfunc(x, y) -> -1, 0, 1'

Exemplo:

>>> lista = ['a', 'c', 'b']
>>> lista.sort()
>>> lista
['a', 'b', 'c']
>>>


Ordenação simples inversa


Para ordenar uma lista do maior para o menor item temos o método 'reverse', que trabalha de maneira análoga ao método 'sort'.

Exemplo:

>>> lista.reverse()
>>> lista
['c', 'b', 'a']
>>>

Ordenação de lista de dicionários por uma chave

O metodo 'sort' aceita uma função de comparação como paramêtro. Essa função caso seja informada será utilizada para comparar os itens da lista e resolver qual item prioridade na ordem sobre os demais.

Para que o método ordene a lista se baseando na chave dos dicionários a função criada sobrescreve a função 'bulti-in' 'cmp' do python.

A função 'cmp' como podemos ver abaixo, compara dois objetos recebidos como paramêtro retornando um inteiro que informa qual dos itens é maior ou se os dois são iguais. Baseado no resultado da função 'cmp' a função 'sort' ira ordenar a lista.

Descrição do método:

'cmp(x, y) -> integer\n\nReturn negative if xy.'

Em nosso exemplo desejamos ordenar a lista utilizando como base a chave (coluna) '2' do dicionário e assim a definimos.

Quando a função 'sort' estiver interando sobre a lista, ela vai passar cada item encontrado para a nossa função sort modificada 'mycmp' que por sua vez irá chamar a funcao 'cmp' passando como paramêtro o valor das chaves no dicionário pelo qual desejamos ordenar.

>>> lista = [{1: 'b', '2': 'b'}, {1: 'c', '2': 'a'}]
>>>
... def mycmp(a, b):
... print a
... print b
... return cmp(a.get('2'), b.get('2'))
...
>>> lista.sort(mycmp)
>>> lista
[{1: 'b', '2': 'b'}, {1: 'c', '2': 'a'}]

Também podemos utilizar 'lambda' para criar uma função anônima. O resultado é o mesmo, talvez com alguma variação de performance.

lista.sort(lambda x,y: cmp(a.get('2'), b('2')))

Ordenar listas de dicionários de acordo com uma ou mais chaves escolhidas

A função abaixo recebe dois paramêtros, a lista que se deseja ordenar e a lista de chaves que devem ditar a ordenação.

>>> lista = [{1: 1, '2': 'b', '3': 0}, {1: 1, '2': 'a', '3': -1}, {1: 0, '2': 'd', '3': 5}]
>>> def sort_list(l, sort_keys):
... def mycmp(a, b):
... k1 = map(lambda x: a.get(x), sort_keys)
... k2 = map(lambda x: b.get(x), sort_keys)
... return cmp(k1, k2)
...
... l.sort(mycmp)
... return l
...
>>> sort_list('1', lista, ['2', '1'])
[{1: 1, '3': -1, '2': 'a'}, {1: 1, '3': 0, '2': 'b'}, {1: 0, '3': 5, '2': 'd'}]

Criamos uma função 'closure' chamada 'mycmp', que como no exemplo anterior será passada para a função 'sort'. Dentro da funcao 'mycmp' é onde temos a diferença básica em relacao a função anterior. Ao invés de dizer qual o resultado de cada dicionário que ele deve utilizar para ordenar, passamos duas listas com os valores das colunas pelas quais desejamos ordenar.

Novamente, o 'sort' utlizando a funcao 'cmp' irá decidir qual dos dois objetos deve ter a preferência e assim ordenar a lista de dicionários de acordo com as colunas que especificamos.

Atenção, alguns módulos como por exemplo 'mxDatetime' sobrescrevem o método sort para que funcionem a seu gosto. Isso pode fazer com o que o método 'cmp' que estiver no namespace local não seja o 'cmp' original, caso você utilize algum módulo onde isso acontece o sort não irá funcionar corretamente. Neste caso deveremos importar a função 'cmp' diretamente das funções 'bulti-in' ou melhor do namespace '__builtin__'.

Detalhe sobre a implementação do método sort

O algoritmo de 'sort' utilizado no python é um tando diferente dos algoritmos tradicionais, um tipo de mergesort com algumas coisas a mais.

Retirado dos fontes do python listsort.txt:

"This describes an adaptive, stable, natural mergesort, modestly called timsort (hey, I earned it ). It has supernatural performance on many kinds of partially ordered arrays (less than lg(N!) comparisons needed, and as few as N-1), yet as fast as Python's previous highly tuned samplesort hybrid on random arrays.

In a nutshell, the main routine marches over the array once, left to right, alternately identifying the next run, then merging it into the previous runs "intelligently". Everything else is complication for speed, and some hard-won measure of memory efficiency.
"

terça-feira, julho 25, 2006

Debugando Python com pdb

Programar sem alguma ferramenta para debug pode ser um grande sofrimento.

Poucas pessoas conhecem ou utilizam, mas o Python, possui um módulo de debug incorparado a biblioteca padrão, chamado pdb:

"The module pdb defines an interactive source code debugger for Python programs. It supports setting (conditional) breakpoints and single stepping at the source line level, inspection of stack frames, source code listing, and evaluation of arbitrary Python code in the context of any stack frame. It also supports post-mortem debugging and can be called under program control."

E para alegria de muitos programadores ele cumpre bem o que promete.

Uso básico:

import pdb; pdb.set_trace()

Quando a linha acima for executadao, o pdb toma conta da situação e seu prompt fica disponivel para que o programador possa debugar seu programa da maneria mais tradicional possivel, o 'set_trace' é um ponto de rastreamento ou captura que colocamos em nosso código.

-> Pdb().set_trace()
(Pdb)

Aqui fica intuitivo os comandos são simples 'n - next', 'c - continue' e assim por diante. Para mais detalhes 'help'.

O inconveniente deste processo é que temos que modificar nossos fontes para poder debugar, adicionando a linha de captura (chamada do metodo set_trace). O esquecimento desta linha no código por exemplo, pode fazer com que o programa fique travado.

Para evitar essas situações podemos utilizar o pdb pela linha de comando no estilo do gdb.

python -m pdb programa.py

Deste modo o prompt vai ser invocado e então podemos dizer em qual linha será colocado o break, ' b 10' por exemplo e depois 'c' para rodar até o break.

Infelizmente a opção '-m' só funciona no python 2.4, para versões mais antigas podemos executar o pdb diretamente da biblioteca informando o arquivo fonte como argumento, no meu caso:

python /usr/local/lib/python2.2/pdb.py main.py

Crie um link simbolico para o pdb e seja feliz.

Existe ainda uma versão do pdb extendida chamada pypdb, que me parece bastante interessante, mas também requer python >= 2.4.0. O pypdb funciona ainda mais na linha do gdb, até mesmo sendo um programa binário externo que invoca o código python a ser debugado.

Para informações mais completas:

O'Reillty - Interactive Debugging in Python
Debugando o Python
Using the Python debugger (pdb)