CURSO DE INFRAESTRUTURA DE CHAVES PÚBLICAS
Ciência da Computação - Universidade de Brasília
Professor Pedro Antonio Dourado de Rezende

TRABALHO FINAL

Autenticação de Usuários e Controle de Acesso


com Certificação Digital

Uma Implementação com OpenSSL

Gelson Heindrikson
José de Ribamar Campos de Sousa
31 de outubro de 2003

1. Escopo


1.1 Demonstrar o uso de certificados digitais para autenticação e controle de acesso de usuários;
2.1 Gerar um programa servidor para essa finalidade;
2.2 Gerar um programa cliente para essa finalidade;
2.3 Testar a implementação utilizando browsers para acessar o programa servidor;
2.4 Testar a implementação utilizando o programa cliente para acessar o programa servidor.

2. Referências

  1. Página Oficial do projeto OpenSSL: http://www.openssl.org
  2. Usando OpenSSL - Uma implementação livre do protocolo SSL: http://www.pedro.jmrezende.com.br/trabs/hammurabi.htm
  3. An Introduction to OpenSSL Programming (Part I): http://www.rtfm.com/openssl-examples/part1.pdf
  4. An Introduction to OpenSSL Programming (Part II): http://www.rtfm.com/openssl-examples/part2.pdf
  5. Cygwin - a Linux-like environment for Windows: http://sources.redhat.com/cygwin

Obs.: Neste trabalho, foi utilizado um programa servidor criado a partir da adaptação do módulo servidor listado na referência 2 acima; já o programa cliente foi criado a partir de uma adaptação do módulo cliente listado na referência 3 acima.

3. Ambiente

A compilação e os testes foram efetuados no sistema operacional Conectiva Linux 9 e também com o Cygwin  executando sob Windows XP e 2000. Possivelmente não requer modificações para compilar/executar sob outros sistemas operacionais UNIX-like.

4. Estrutura de diretórios

O arquivo de recursos (vide Apêndice A), contém todos os programas necessários para o leitor interessado em repetir os passos detalhados nesse roteiro. De forma resumida, o conteúdo está estruturado conforme abaixo:

/ca
          /ca.config
          /demoCA
          /<diversos scripts *.sh para gerar chaves e certificados>
/cliente
          /wclient.c
          /<demais arquivos fontes *.c e *.h do programa cliente>
/servidor
          /area1
          /area2
          /keys
          /servidor.c
          /<demais arquivos fontes *.c e *.h do programa servidor>
/<scripts *.sh para copiar certificados e chaves para os diretórios das aplicações cliente e servidor>


5. Arquivo "ca.config"

O arquivo "ca.config" foi adaptado a partir do original, que normalmente fica em /etc/ssl/openssl.cnf.  As principais alterações foram para incluir OIDs específicos na extensão Extended Key Usage, que servirão para identificar o perfil de acesso do usuário cliente.
...
...
# FAKE OIDS CRIADOS A PARTIR DO OBJ_ID_KP DA PKIX
OBJ_id_kp=1.3.6.1.5.5.7.3
fake_oid_user_type1=${OBJ_id_kp}.999
fake_oid_user_type2=${OBJ_id_kp}.998
...
...
[ cli_cert1 ]
...
#extended key usage para um usuario cliente type1
extendedKeyUsage = clientAuth, codeSigning, emailProtection, fake_oid_user_type1
...
...

[ cli_cert2 ]
...
#extended key usage para um usuario cliente type2
extendedKeyUsage = clientAuth, codeSigning, emailProtection, fake_oid_user_type2


6. Roteiro para criação de chaves e certificados

6.1 Criação da chave e certificado do CA

Sob o diretório "ca", executar o script 'geraca.sh', o qual irá executar os seguintes comandos:

openssl genrsa  -des3 -out chave_privada_ca.pem 2048

cp chave_privada_ca.pem ./demoCA/private/cakey.pem

openssl req -new -x509 -config ca.config -key chave_privada_ca.pem -out certificado_ca.pem -days 365


cp certificado_ca.pem ./demoCA/cacert.pem

Observações:
Após execução desse script, a chave privada do CA estará no arquivo "chave_privada_ca.pem" e o certificado do CA estará no arquivo "certificado_ca.pem". Cópias desses arquivos também foram feitas, respectivamente em  "./demoCA/private/cakey.pem" e "./demoCA/cacert.pem" (é onde o utilitário 'openssl'  irá buscar tais dados quando o CA assinar certificados - conforme configurado em 'ca.config').

6.2 Criação da chave e certificado do Servidor

Sob o diretório "ca", executar o script 'geraserv.sh',   o qual irá executar os seguintes comandos:

openssl genrsa -des3 -out chave_privada_sv.pem 2048

openssl req -new -config ca.config -key chave_privada_sv.pem -out pedido_sv.pem

openssl ca -config ca.config -extensions usr_cert  -out certificado_sv.pem -in pedido_sv.pem

Observações:
Após execução desse script a chave privada do Servidor estará no arquivo  "chave_privada_sv.pem", e o certificado, assinado pelo CA, estará em "certificado_sv.pem".

6.3 Criação da chave e certificado do Cliente UM

Sob o diretório "ca", executar o script 'geracli1.sh',   o qual irá executar os seguintes comandos:

openssl genrsa -des3 -out chave_privada_cli1.pem 2048

openssl req -new -config ca.config -key chave_privada_cli1.pem -out pedido_cli1.pem

openssl ca -config ca.config -extensions cli_cert1 -out certificado_cli1.pem -in pedido_cli1.pem

openssl pkcs12 -export -clcerts -in certificado_cli1.pem -inkey chave_privada_cli1.pem -out certificado_cli1.pfx

Observações:
Após execução desse script a chave privada do Cliente UM estará no arquivo  "chave_privada_cli1.pem", e o certificado, assinado pelo CA, estará em "certificado_cli1.pem".

6.4 Criação da chave e certificado do Cliente DOIS

Sob o diretório "ca", executar o script 'geracli2.sh',   o qual irá executar os seguintes comandos:

openssl genrsa -des3 -out chave_privada_cli2.pem 2048

openssl req -new -config ca.config -key chave_privada_cli2.pem -out pedido_cli2.pem

openssl ca -config ca.config -extensions cli_cert2 -out certificado_cli2.pem -in pedido_cli2.pem

openssl pkcs12 -export -clcerts -in certificado_cli2.pem -inkey chave_privada_cli2.pem -out certificado_cli2.pfx

Observações:
Após execução desse script a chave privada do Cliente UM estará no arquivo  "chave_privada_cli2.pem", e o certificado, assinado pelo CA, estará em "certificado_cli2.pem".


7. Alterações nos programas do módulo Servidor

7.1 Alterações no arquivo "utils.c"

Foi incluído a seguinte chamada, na função inicializar_contexto() para que o servidor solicite um certificado ao cliente durante o 'handshake' SSL:

  /* solicita autenticacao do cliente */
  SSL_CTX_set_verify(contexto,SSL_VERIFY_PEER,0);

Foi incluída a função pegar_dados_peer() abaixo, para pegar os dados do certificado do cliente:

/* Se o peer enviou certificado, pega o common-name e le todos obj-id que
   compoem a extensao extended-key-usage, verifica se o obj-id tem o
   prefixo que nós estamos esperando, copia para o buffer o obj-id com maior
   valor, em formato string. Retorna -1 se peer nao enviou certificado */
int pegar_dados_peer(SSL *ssl, char *str_exku, int tam1, char *cn_peer, int tam2){
  char temp_buf[32] = "";
  X509 *peer;
  EXTENDED_KEY_USAGE *extusage;
  ASN1_OBJECT *obj;
  int idx;

   memset(str_exku,0,tam1);
   memset(cn_peer,0,tam2);

   printf("Vou pegar certificado do peer\n");
   peer=SSL_get_peer_certificate(ssl);
    //pode ser null, pois foi solicitado, nao exigido:
   if(peer == NULL) {
        return -1;
   }

    /* pega o common-name do peer */
    X509_NAME_get_text_by_NID(X509_get_subject_name(peer), NID_commonName, cn_peer, tam2);

    /* pega o extended-key-usage com prefixo correto e maior valor */
    printf("Iniciando loop para checar extended-key-usage\n");
    if((extusage=X509_get_ext_d2i(peer, NID_ext_key_usage, NULL, NULL))) {
    for(idx = 0; idx < sk_ASN1_OBJECT_num(extusage); idx++) {
           obj = sk_ASN1_OBJECT_value(extusage,idx);
           OBJ_obj2txt(temp_buf, sizeof(temp_buf), obj, 1);
           printf("Encontrado objid= %s\n",temp_buf);
       //ve se oid inicia com o prefixo desejado:
           if(strstr(temp_buf, PREF_OID_CLI) == temp_buf) {
           //printf("prefix match!\n");
             //mantem no buffer de retorno a string com o maior valor:
           if(strcmp(str_exku, temp_buf) < 0) {
               //printf("str_exku e' menor, vou copiar\n");
             strncpy(str_exku,temp_buf,tam1-1);
           }
       }

    }
    sk_ASN1_OBJECT_pop_free(extusage, ASN1_OBJECT_free);
    }

   //desaloca memoria!
   X509_free(peer);

   return 0;

  }

7.1 Alterações no arquivo "servidor.c"

Foi incluída a função validar_acesso() abaixo, para validar o acesso ao recurso solicitado pelo cliente. Isso é feito chamando-se a função  pegar_dados_peer()para ler o certificado do cliente, em seguida é verificada a extensão Extended Key Usage do certificado . Aos clientes que tem nesta extensão um OID_CLI_TYPE1 é dado acesso a qualquer arquivo sob a árvore de diretório "area1" do servidor.  Já aos clientes com um OID_CLI_TYPE2 é dado acesso a qualquer arquivo sob a árvore de diretório "area2" do servidor.
Caso o cliente não apresente um certificado válido, é enviada uma página de erro ao cliente.

//Autentica cliente e verifica acesso ao arquivo solicitado,
//retorna em nome_arquivo_ret o proprio arquivo solicitado ou
//index ou pagina de erro, conforme o acesso do usuario.
//Pode retornar um header, conforme o caso.
void validar_acesso(SSL *ssl, char *inicio_nome_arquivo,
            char *nome_arquivo_ret, int tam_nome, char *header) {
    char cn_peer[32];
    char ext_key_usage[32];
    //char *desculpe ="<html><body style=\"background-color: rgb(255, 255, 204);\"><h2>Desculpe sr(a) usuario(a) %s,</h2><br></body></html>\r\n";
    char *desculpe ="<html><body><h2>Desculpe sr(a) usuario(a) %s,</h2><br></body></html>\r\n";
    //char *bemvindo ="<html><body style=\"background-color: rgb(156, 255, 255);\"><h2>Bemvindo sr(a) usuario(a) %s,</h2><br></body></html>\r\n";
    char *bemvindo ="<html><body><h2>Bemvindo sr(a) usuario(a) %s,</h2><br></body></html>\r\n";


    memset(ext_key_usage,0, sizeof(ext_key_usage));

    /* pega extended key usage do peer, se enviou certif.*/
    if(pegar_dados_peer(ssl, ext_key_usage, sizeof(ext_key_usage),
                      cn_peer, sizeof(cn_peer)) != -1 ) {
       printf("CommonName do peer: %s\n", cn_peer);
       printf("Extended key usage a ser usado: %s\n",ext_key_usage);
    } else {
       printf("Peer nao enviou certificado\n");
    }

    if(strcmp(ext_key_usage, OID_CLI_TYPE1) == 0) {
        if( ! (strstr(inicio_nome_arquivo, AREA1) == inicio_nome_arquivo)) {
          //esta' tentando acessar recurso fora da area permitida
          //header[0] = '\0';
          sprintf(header, desculpe, cn_peer);
          strncpy(nome_arquivo_ret, "erro1.html", tam_nome);
      } else { //acesso ao recurso ok
          if(strstr(inicio_nome_arquivo, AREA1"/index1.html") != NULL ) {
             sprintf(header, bemvindo, cn_peer); //esta pedindo o index, adiciona bemvindo
             strncpy(nome_arquivo_ret, inicio_nome_arquivo, tam_nome);
          } else {
             header[0] = '\0';//esta pedindo um arquivo normal, nao ha header
             strncpy(nome_arquivo_ret, inicio_nome_arquivo, tam_nome);
          }
      }
    } else {
          if(strcmp(ext_key_usage, OID_CLI_TYPE2) == 0) {
             if( ! (strstr(inicio_nome_arquivo, AREA2) == inicio_nome_arquivo)) {
                //esta' tentando acessar recurso fora da area permitida
                sprintf(header, desculpe, cn_peer);
                strncpy(nome_arquivo_ret, "erro2.html", tam_nome);
             } else { //acesso ao recurso ok
                if(strstr(inicio_nome_arquivo, AREA2"/index2.html") != NULL ) {
                    sprintf(header, bemvindo, cn_peer); //esta pedindo o index, adiciona bemvindo
                    strncpy(nome_arquivo_ret, inicio_nome_arquivo, tam_nome);
                } else {
                    header[0] = '\0';//esta pedindo um arquivo normal, nao ha header
                    strncpy(nome_arquivo_ret, inicio_nome_arquivo, tam_nome);
                }
             }
          }else { //nao possui nenhum dos OID acima ou nao enviou certificado
             sprintf(header, desculpe, cn_peer);
             strncpy(nome_arquivo_ret, "errocert.html", tam_nome);
          }
    }
    printf("Arquivo a ser retornado: %s\n", nome_arquivo_ret);
    return;
}


Se  o cliente estiver solicitando um arquivo situado fora da sua área de acesso, é enviada uma mensagem de erro contendo um link para o menu de opções permitidas;
Se o cliente estiver solicitando o arquivo "index" dentro da sua área, é enviada uma saudação e o menu de opções;
Se o cliente estiver solicitando outro arquivo qualquer dentro da sua área de acesso, é enviado o conteúdo do arquivo.

Algumas páginas  são montadas dinamicamente, de forma a incluir uma saudação contendo o Common Name do cliente, que foi lido do certificado. Para isso, foi alterada a assinatura da função  transmitir_arquivo(), incluindo um parâmetro com um header (a saudação) a ser transmitido antes do conteúdo do arquivo:

int transmitir_arquivo(char *nome_arquivo, BIO *bio_buffer_ssl, char *header) {

e o seguinte trecho de código foi inserido naquela função:

  if(strlen(header) != 0) { //adiciona o header antes de enviar o arquivo
      if(BIO_puts(bio_buffer_ssl, header) <= 0) {
           ERR_print_errors(bio_stderr);
           return -1;
      }
 }

8. Alterações nos programas do módulo cliente

8.1 Alterações no arquivo "wclient.c"

O trecho abaixo foi modificado, para incluir as opções "-f" que receberá o nome do arquivo, e a opção "-j" que receberá um flag que informa se o programa cliente enviará ou não um certificado ao servidor.

    while((c=getopt(argc,argv,"h:p:f:i:j"))!=-1){

      switch(c){
        case 'h':
          if(!(host=strdup(optarg)))
            err_exit("Out of memory (host)");
          break;
        case 'p':
          if(!(port=atoi(optarg)))
            err_exit("Bogus port specified");
          break;
        case 'f':
          if(!(file=strdup(optarg)))
            err_exit("Out of memory (file)");
          break;
        case 'i':
          require_server_auth=0;
          break;
        case 'j':
          require_client_auth=0;
          break;
      }
    }

O trecho abaixo foi alterado, para inicializar o contexto com ou sem um arquivo de chave/certificado:

    if(require_client_auth) {
       ctx=initialize_ctx(KEYFILE,PASSWORD);
    } else {
       /* nao usaremos certificado do cliente: */
       ctx=initialize_ctx(NULL,PASSWORD);
    }


8.2 Alterações no arquivo "commons.c"

O segmento abaixo foi alterado, de forma que as funções SSL_CTX_use_certificate_chain_file()  e SSL_CTX_use_PrivateKey_file()são usadas apenas quando um arquivo de chave/certificado foi informado:

 /* se keyfile vazio, nao usaremos este arquivo */
 if(keyfile != NULL) {
    printf("Vou chamar use_certificate_chain_file em %s\n", keyfile);
    if(!(SSL_CTX_use_certificate_chain_file(ctx, keyfile)))
      berr_exit("Can't read certificate file");
 }

    pass=password;
 /* se keyfile vazio, nao usaremos este arquivo */
 if(keyfile != NULL) {
    printf("Vou chamar use_PrivateKey_file em %s\n", keyfile);
    SSL_CTX_set_default_passwd_cb(ctx, password_cb);
    if(!(SSL_CTX_use_PrivateKey_file(ctx, keyfile,SSL_FILETYPE_PEM)))
      berr_exit("Can't read key file");
 }


9. Compilação dos módulos cliente e servidor

Os módulos servidor e cliente podem ser compilados, respectivamente, com os comandos abaixo:

No diretório 'servidor':
gcc -o servidor servidor.c utils.c -lssl -lcrypto

No diretório 'cliente':
gcc -o wclient wclient.c common.c read_write.c client.c -lcrypto -lssl


10. Roteiro para teste de acesso utilizando browser

10.1 Inicializando o servidor

Antes de executar o programa servidor é preciso rodar o script  "gera_arquivos_servidor.sh",  localizado na raiz do diretório do arquivo de recursos. Ele efetua uma cópia das chaves e certificados do CA e do Servidor para os locais esperados pelo programa:

# copia os arquivos necessarios ao funcionamento do
# programa 'servidor' para os diretorios corretos
cp ca/certificado_ca.pem  servidor/keys/certificado_ca.pem
cp ca/certificado_sv.pem  servidor/keys/certificado_sv.pem
cp ca/chave_privada_sv.pem  servidor/keys/chave_privada_sv.pem

Feito isso, vá para o diretório 'servidor' e digite o comando abaixo, para inicializar o servidor na porta 9999:

./servidor 9999


Será solicitada a senha da chave privada do servidor: informe "password" ou outra utilizada na geração da chave privada do servidor (ver seção 6.2).

10.1 Importando o certificado do CA no Mozilla

Utilize o menu "Editar/Preferências" e selecione, dentro da categoria "Privacidade", a opção "Certificados", conforme a figura abaixo:

Preferências

Clique no botão "Abrir" na seção "Gerenciador de Certificados", para que seja apresentada a tela seguinte:

Gerenciador de certificados

Para importar o certificado do CA, selecione a aba "Autoridades" e clique no botão "Importar". Navegue até o diretório 'ca' dentro da estrutura de diretórios do arquivo de recursos e selecione o arquivo "certificado_ca.pem". Será apresentado o dialog a seguir:

Efetuando...

Marque todas as finalidades listadas e clique em "OK" para concluir a importação do certificado do CA.


10.2 Acessando o servidor sem instalar um certificado de cliente

Ao tentarmos acessar uma URL válida no servidor (por exemplo: https://localhost:9999/utils.h), antes de instalarmos um certificado de cliente no browser, obteremos uma resposta semelhante à abaixo:

Sem certificado instalado

10.3 Importando o certificado do Cliente UM no Mozilla

Para importar o certificado do Cliente UM, selecione a aba "Seus certificados" e clique no botão "Importar". Navegue até o diretório 'ca' dentro da estrutura de diretórios do arquivo de recursos e selecione o arquivo "certificado_cli1.pfx".  Serão solicitadas duas senhas:

 Os dados do certificado deverão aparecer na lista do gerenciador, como abaixo:

Seus certificados...


10.4 Acessando uma página não autorizada 

Tendo importado o certificado do cliente UM, vamos tentar acessar uma página válida mas não autorizada (por exemplo: https://localhost:9999/utils.h). O resultado deve ser semelhante ao abaixo:

Sem acesso ao recurso
 

10.5 Acessando páginas autorizadas para o cliente UM   

Ao acessar uma página válida e autorizada  para o cliente UM (por exemplo: https://localhost:9999/area1/index1.html), o resultado deve ser semelhante ao abaixo:

Bemvindo um

10.6 Acessando páginas autorizadas para o cliente DOIS  

Antes de executar este passo, deve-se importar o certificado do Cliente DOIS no browser, de forma similar ao que foi feito na seção 10.3, atentando para que seja importado o arquivo "certificado_cli2.pfx").
Ao acessar uma página válida e autorizada  para o Cliente DOIS (por exemplo: https://localhost:9999/area2/index2.html), o  resultado deve ser semelhante ao abaixo:

Bemvindo dois

10.7 Importando os certificados no Internet Explorer e acessando o servidor

Acesssar o menu "Ferramentas/Opções da Internet" e, após abertura do dialog selecionar a aba "Conteúdo" e clicar no botão "Certificados". A partir daí os passos são semelhantes aos procedimentos descritos para o Mozilla. O resultado dos testes de acesso são idênticos aos acima.

11. Roteiro para teste de acesso utilizando o programa cliente

Antes de executar o programa cliente é preciso rodar o script "gera_arquivos_cliente.sh", localizado na raiz do diretório do arquivo de recursos. Ele concatena a chave e o certificado do cliente e gera um arquivo único, como esperado pelo programa:

# Junta os certificados com as chaves privadas,
# conforme esperado pelos programas em 'cliente':

cat ca/certificado_ca.pem > cliente/root.pem
cat ca/certificado_cli1.pem ca/chave_privada_cli1.pem > cliente/client.pem
cat ca/certificado_sv.pem  ca/chave_privada_sv.pem  > cliente/server.pem

Feito isso, vá para o diretório 'cliente' e digite o comando abaixo, para tentar acessar uma página válida mas sem acesso permitido:

./wclient -h localhost -p 9999 -f utils.h

O resultado é a página HTML abaixo, a qual informa que o usuário  (cliente UM) foi identificado, que o recurso não é acessível,  e um link para o menu de opções::

HTTP/1.0 200 OK

<html><body><h2>Desculpe sr(a) usuario(a) clienteUM,</h2><br></body></html>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body style="background-color: rgb(255, 255, 204); color: rgb(0, 0, 0);"
 link="#0000ee" alink="#0000ee" vlink="#551a8b">
<h3>O recurso solicitado n&atilde;o est&aacute; dispon&iacute;vel para
o seu perfil de usu&aacute;rio.</h3>
<span style="font-weight: bold;"><br>
Estas s&atilde;o as op&ccedil;&otilde;es de servi&ccedil;o
dispon&iacute;veis para seu perfil: </span><a
 href="https://localhost:9999/area1/index1.html"
 style="font-weight: bold;">index1</a><span style="font-weight: bold;">.</span>
br>
<br>
<br>
OBS - O certificado apresentado em sua autentica&ccedil;&atilde;o
possui Extended Key Usage = OID_CLI_TYPE1<br>
<br>
<span style="font-style: italic;"> <br>
<br style="font-style: italic;">
</span><br>
</body>
</html>

Em seguida, tente o comando abaixo, para tentar acessar uma página válida e com acesso permitido:

./wclient -h localhost -p 9999 -f area1/index1.html

A resposta deve ser a página  solicitada, tal como abaixo:

HTTP/1.0 200 OK

<html><body><h2>Bemvindo sr(a) usuario(a) clienteUM,</h2><br></body></html>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
  <meta http-equiv="content-type"
 content="text/html; charset=ISO-8859-1">
  <title>index1</title>
</head>
<body style="color: rgb(0, 0, 0); background-color: rgb(153, 255, 255);"
 link="#0000ee" alink="#0000ee" vlink="#551a8b">
<span style="font-style: italic;"></span>
<h2>Estas s&atilde;o as op&ccedil;&otilde;es de servi&ccedil;o
dispon&iacute;veis para seu perfil de usu&aacute;rio:<br>
</h2>
<br>
<a href="servico_1.1.html"
 style="color: rgb(255, 102, 0); font-weight: bold;">Servi&ccedil;o 1.1</a><br>
<br>
<br style="color: rgb(255, 102, 0);">
<a href="servico_1.2.html"
 style="color: rgb(255, 102, 0); font-weight: bold;">Servi&ccedil;o 1.2</a><br>
<br>
<br>
<br>
OBS - O certificado apresentado em sua autentica&ccedil;&atilde;o
possui Extended Key Usage = OID_CLI_TYPE1<br>
<br>
<span style="font-style: italic;"> <br>
<br style="font-style: italic;">
</span><br>
</body>
</html>



12. Considerações Finais

Os programas aqui utilizados são funcionais, mas sua finalidade é didática sendo, por isso, bastante simplificados. Para uma implementação real, num ambiente de produção, muitas melhorias devem ser consideradas, dentre as quais:
Também convém lembrar que está em andamento a especificação de um "Certificados de Atributos" para conter informações sobre autorizações de usuários. Este tipo de certificado será sempre utilizado em conjunto com um certificado X509 padrão, que continua  tendo a  função de identificar (autenticar) o usuário.

Quanto ao uso de um programa cliente em lugar de um browser, é importante observar que isto pode ser necessário em algumas situações, tais como:


Apêndice A

Arquivo contendo os programas servidor e cliente, scripts e acessórios: recursos.zip