libgsasl的函数使用笔记 - tRavAsty/SASL GitHub Wiki

libgsasl是GNU开发的一个C语言第三方库。它的机制种类要比JAVA的API多,包含ANONYMOUS, CRAM-MD5, DIGEST-MD5, EXTERNAL, GS2-KRB5, GSSAPI, LOGIN, NTLM, PLAIN, SCRAM-SHA-1, SCRAM-SHA-1-PLUS, SAML20, OPENID20, and SECURID 机制

由于是GNU编写的第三方库,libgsasl需要自己安装,网址在这里,linux系统解压最新版本就好了,里面还会附带例程,对于了解libgsasl的使用很有帮助 ###ligsasl在linux下的安装

linux下的命令操作安装libgsasl

     $ wget -q ftp://ftp.gnu.org/gnu/gsasl/gsasl-1.8.0.tar.gz
     $ tar xfz gsasl-1.8.0.tar.gz
     $ cd gsasl-1.8.0/
     $ ./configure
     ...
     $ make
     ...
     $ make install
     ...

注意,make install命令默认是安装在/usr/local下的,所以不是编译器链接的默认路径,编译会找不到libgsasl库。有两个方法,一个是改变/etc/ld.so.conf ldconfig PKG_CONFIG_PATH,具体操作和解释看这里;另一个是在配置的时候执行./configure --prefix=/usr,把库安装在/usr目录下。如果是自己用gcc编译文件需要加上-lgsasl选项。我通常图简单选第二种= =

###编写程序

包含头文件 所有要用到libgsasl的程序都需要包含<gsasl.h>库,具体如下

#include <gsasl.h>

由于C语言是面向过程的,所以定义Gsasl *ctx为gsasl认证的工作环境,环境的初始化需要用到gsasl_init (&ctx)函数,这个函数会返回一个整形常量值,用rc表示。rc会返回两种值,一个是成功的消息GSASL_OK,失败则返回GSASL_MALLOC_ERROR 经常使用的代码片段像这样

     int
     main (int argc, char *argv[])
     {
       Gsasl *ctx = NULL;
       int rc;
     ...
       rc = gsasl_init (&ctx);
       if (rc != GSASL_OK)
         {
           printf ("SASL initialization failure (%d): %s\n",
                   rc, gsasl_strerror (rc));
           return 1;
         }
     ...

GSASL 只是一个进程级别的环境,libgsasl提供线程级别的环境,使得开销更小。线程级别的实现就是Gsasl_session *session。这个session必须有确定的机制以及相应机制的属性,机制类别和属性会在后文提到。 一个session的例子

void client (Gsasl *ctx)
     {
       Gsasl_session *session;
       const char *mech = "PLAIN";
       int rc;
     
       /* Create new authentication session. */
       if ((rc = gsasl_client_start (ctx, mech, &session)) != GSASL_OK)
         {
           printf ("Cannot initialize client (%d): %s\n",
                   rc, gsasl_strerror (rc));
           return;
         }
     
       /* Set username and password in session handle.  This info will be
          lost when this session is deallocated below.  */
       gsasl_property_set (session, GSASL_AUTHID, "jas");
       gsasl_property_set (session, GSASL_PASSWORD, "secret");//到这一步,PLAIN机制所需要的session环境就已经配置好
       
       /* Do it. */
       client_authenticate (session);
     
       /* Cleanup. */
       gsasl_finish (session);
     }

配置好session之后,如何进行认证的步骤呢?其实,认证步骤根据是否客户端优先以及认证机制都有关系。基本上所有机制的第一条消息都可以由或者服务器或者客户端发起。只不过由于GSASL是认证客户端身份的协议框架,所以服务器发送的是挑战,客户端发送的是应答或者请求挑战,有时,由于客户端第一条消息的固定,所以客户端直接发送第一条默认挑战的应答来节省开销。(例如,先发送自己的用户名)

客户端和服务器的认证基本上都靠int gsasl_step64 (Gsasl_session * sctx, const char * b64input, char ** b64output)来实现,这个函数是面向过程的,因此需要通过有具体机制的GSasl_session来判断对输入(也许是挑战,也许是应答)的处理。输出的是一个字节数组。输入和输出都需要用BASE64编码表示。这比直接传输明文有着更好的容错性。函数的返回值是一个整形常量,有两个非错结果,一个是GSASL_OK,表示认证成功,一个是GSASL_NEEDS_MORE表示需要更多信息,这通过服务器和客户端的交互来完成。如果出错,则返回错误数字(gsasl_step也可以实现类似功能)

一个典型的例子如下

     void client_authenticate (Gsasl_session * session)
     {//这个函数实现的是一个由服务器先发送消息的机制
       char buf[BUFSIZ] = "";
       char *p;
       int rc;
     
       /* This loop mimics a protocol where the server sends data
          first. */
     
       do
         {
           printf ("Input base64 encoded data from server:\n");
           fgets (buf, sizeof (buf) - 1, stdin);
           if (buf[strlen (buf) - 1] == '\n')
             buf[strlen (buf) - 1] = '\0';
     
           rc = gsasl_step64 (session, buf, &p);
     
           if (rc == GSASL_NEEDS_MORE || rc == GSASL_OK)
             {
               printf ("Output:\n%s\n", p);
               free (p);
             }
         }
       while (rc == GSASL_NEEDS_MORE);
     
       printf ("\n");
     
       if (rc != GSASL_OK)
         {
           printf ("Authentication error (%d): %s\n",
                   rc, gsasl_strerror (rc));
           return;
         }
     
       /* The client is done.  Here you would typically check if the
          server let the client in.  If not, you could try again. */
     
       printf ("If server accepted us, we're done.\n");
     }

###选择一个机制 libgsasl里面有一个函数int gsasl_server_mechlist (Gsasl * ctx, char ** out)返回服务器所支持的机制名称,用空格分开。同样有一个对应的int gsasl_client_mechlist (Gsasl * ctx, char ** out)返回客户端所支持的机制名称。服务器和客户端的机制可能不包含全部libgsasl库所支持的机制,出于安全的考虑。 当然libgsasl库有专门为了安全考虑而建议使用的机制的函数。int gsasl_client_support_p (Gsasl * ctx, const char * name)int gsasl_server_support_p (Gsasl * ctx, const char * name)来提供对name名称里的机制的建议。name同样是用空格区分的机制名称。安装了libgsasl库以后,进入解压的gsasl文件夹,再进入examples文件夹,可以运行具体的例子。代码如下

cd exmaples
make client-mech
./client-mech

然后输入机制名称即可

###使用回调

之前使用PLAIN机制的时候,用户名和密码是通过硬编码嵌入程序中的,不立于程序的模块化和耦合。这个时候就可以采用回调来使得程序更具通用性。C语言可以传递函数,所以回调的编写方法我感觉要比JAVA更简洁。回调会根据属性要求来实现对应的回应,属性和机制的选取有关。像PLAIN机制当然最必不可少的就是用户名和密码啦 回调编写的例子

     int callback (Gsasl * ctx, Gsasl_session * sctx, Gsasl_property prop)
     {
       char buf[BUFSIZ] = "";
       int rc = GSASL_NO_CALLBACK;
     
       /* Get user info from user. */
     
       printf ("Callback invoked, for property %d.\n", prop);
     
       switch (prop)
         {
         case GSASL_PASSCODE:
           printf ("Enter passcode:\n");
           fgets (buf, sizeof (buf) - 1, stdin);
           buf[strlen (buf) - 1] = '\0';
     
           gsasl_property_set (sctx, GSASL_PASSCODE, buf);
           rc = GSASL_OK;
           break;
     
         case GSASL_AUTHID:
           printf ("Enter username:\n");
           fgets (buf, sizeof (buf) - 1, stdin);
           buf[strlen (buf) - 1] = '\0';
     
           gsasl_property_set (sctx, GSASL_AUTHID, buf);
           rc = GSASL_OK;
           break;
     
         default:
           printf ("Unknown property!  Don't worry.\n");
           break;
         }
     
       return rc;
     }

使用gsasl_callback_set (ctx, callback)的方法在上下文中嵌入回调

GSASL认证结束后记得要用 gsasl_done (ctx)来清理内存空间哦

###机制以及它的属性

EXTERNAL机制

EXTERNAL机制通常被用在绑定TLS的过程中,来使得Server可以认证客户端身份


服务器端要求属性:GSASL_VALIDATE_EXTERNAL回调

客户端要求属性:GSASL_AUTHZID

ANONYMOUS机制

ANONYMOUS机制是用来“认证”使用匿名服务的客户端的机制。代表客户打算匿名地使用这个服务


服务器端要求属性:GSASL_VALIDATE_ANONYMOUS回调

客户端要求属性:GSASL_ANONYMOUS_TOKEN(可以被服务器存入log日志)

PLAIN机制

PLAIN机制使用用户名和密码来认证客户。这里有两个用户名相关,一个是认证ID,来代表密钥的拥有者,另一个是授权ID,通常为空,来代表想要行驶的用户身份,详见这里。如果授权ID为空的话,表示用户代表他自己。


服务器端要求属性:GSASL_VALIDATE_SIMPLE回调

客户端要求属性:GSASL_AUTHID GSASL_PASSWORD

LOGIN机制

LOGIN机制是一个非标准的机制,类似于PLAIN机制但是没有授权ID

CRAM-MD5机制

CRAM-MD5是被广泛使用的一种机制,传输哈希过的密码而不是明文,gnu官网上说它已经被官方废弃了,不过我并没有在RFC文档中看到废弃的消息RFC2195,反倒是DIGEST-MD5(目的为了取代CRAM-MD5的机制)被废除了,RFC见这里,中文部分翻译见这里。详细的CRAM-MD5解释见这里


服务器端要求属性:GSASL_PASSWORD回调 GSASL_AUTHID属性

客户端要求属性:GSASL_AUTHID GSASL_PASSWORD

DIGEST-MD5机制

DIGEST-MD5使用重复的哈希计算,这种机制在MD5被破解了之后可能是它的弱点,也许安全强度要比HMAC-MD5更低,但是DIGEST-MD5支持更多特性。例如,提供认证实体和数据完整性保护。而CRAM-MD5只提供哈希过的密钥而已。因此,DIGEST-MD5需要使用正确的密码存储来认证客户的消息。


服务器端要求属性:GSASL_DIGEST_MD5_HASHED_PASSWORD回调 GSASL_PASSWORD回调 GSASL_QOPS回调

客户端要求属性:GSASL_AUTHID GSASL_PASSWORD GSASL_SERVICE GSASL_HOSTNAME,如果设置了的话,GSASL_AUTHZID和GSASL_REALM也会被使用 GSASL_QOP回调来得到保护质量的值(quality of protection,qop)。如果没有设置这个回调,默认使用qop-auth


然而DIGEST-MD5已经被IETF废除了,下面这个是RFC文档中说可以取代DIGEST-MD5的东西

SCRAM-SHA-1机制

(这个机制javax.secuirty.sasl.*里面没有!)SCRAM-SHA-1是用来提供(基本上是一样的)类似CRAM-MD5和DIGEST-MD5的机制的功能,但是使用了现在先进的类似HMAC-SHA-1技术作为哈希算法,以及PKCS#5 PBKDF2标准中的密钥派生。SCRAM-SHA-1提供认证实体。就像CRAM-MD5和DIGEST-MD5一样,它只传输哈希过的密码。因此,SCRAM-SHA-1需要连接正确密码的通道,该机制同时支持信道绑定。


服务器端要求属性:GSASL_PASSWORD回调 GSASL_SCRAM_ITER和GSASL_SCRAM_SALT属性 当GSASL_CB_TLS_UNIQUE属性被设定的时候,通道就会支持SCRAM-SHA-1-PLUS机制并且协商信道绑定

客户端要求属性:GSASL_AUTHID GSASL_PASSWORD/GSASL_SCRAM_SALTED_PASSWORD

NTLM机制

这不是一个标准机制。不要再新的应用中使用它。也不要设想他是安全的。目前只有客户端支持这个机制


客户端要求属性:GSASL_AUTHID GSASL_PASSWORD

SECUREID机制

SECUREID机制既使用授权ID也使用认证ID带着一个由硬件令牌传递的密码来认证用户


服务器端要求属性:GSASL_VALIDATE_SECURID回调

客户端要求属性:GSASL_AUTHID GSASL_PASSCODE GSASL_AUTHZID(可能) GSASL_PIN(可能)

GSSAPI机制

GSSAPI允许你使用Kerberos V5。这个机制最初是为了允许任何的GSS-API机制被使用,但是协议的问题使得这个功能并不现实,因此被严格地限定在Kerberos V5中。查看GS2-KRB5来查看更一般的实现。


服务器端要求属性:GSASL_SERVICE GSASL_HOSTNAME属性 GSASL_VALIDATE_GSSAPI回调

客户端要求属性:GSASL_AUTHID GSASL_SERVICE GSASL_HOSTNAME (必须获得Kerberos的凭证)

GS2-KRB5机制

GS2是一个SASL和GSS-API之间的协议网桥,允许任何支持相互认证和信道绑定的GSS-API作为SASL机制使用。目前ligsasl库只支持GS-KRB5机制(用于Kerberos V5的认证),但是可以方便地支持其他的GSS-API机制


服务器端要求属性:GSASL_SERVICE GSASL_HOSTNAME属性 GSASL_VALIDATE_GSSAPI回调

客户端要求属性:GSASL_AUTHID GSASL_SERVICE GSASL_HOSTNAME (必须获得Kerberos的凭证)

SAML20机制

SAML20使得在SASL中可以成功使用SAML(介是什么东东),使用吧认证交换卸载到外部的浏览器的方式。协议在RFC6595中详细描述


服务器端要求属性: GSASL_SAML20_REDIRECT_URL回调

客户端要求属性:GSASL_AUTHID GSASL_SAML20_IDP_iDENTIFIER 属性

注意到SAML本身并没有被GNU SASL库实现,客户端并不需要任何SAML的知识,但是服务器端需要调用SAML库。在examples里面有一个完整的SAML例子。gsasl命令行可以作为连接SAML20的客户端

OPENID20机制

OPENID20机制使得在SASL中使用OpenId成为了可能,使用把认证交换卸载到外部的浏览器中,协议的具体实施在RFC6616


服务器端要求属性:GSASL_OPENID20_REDIRECT_URL属性 GSASL_VALIDATE_OPENID20回调

客户端要求属性:GSASL_AUTHID(需要包含User-Supplied OpenID Identifier)

OpenID并没有被GNU SASL库实现,只要求服务器端有OpenID的实现

这里是更详细的机制和属性介绍(英文)

⚠️ **GitHub.com Fallback** ⚠️