E-MailRelay

gcram.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gcram.cpp
19///
20
21#include "gdef.h"
22#include "gcram.h"
23#include "ghash.h"
24#include "ghashstate.h"
25#include "gmd5.h"
26#include "gstr.h"
27#include "gssl.h"
28#include "gbase64.h"
29#include "glocal.h"
30#include "gexception.h"
31#include "gtest.h"
32#include "glog.h"
33#include <algorithm>
34
35 namespace GAuth
36{
37 namespace CramImp /// An implementation namespace for GAuth::Cram.
38 {
39 GSsl::Library & lib()
40 {
41 GSsl::Library * p = GSsl::Library::instance() ;
42 if( p == nullptr ) throw G::Exception( "no tls library" ) ;
43 return *p ;
44 }
45 struct DigesterAdaptor /// Used by GAuth::Cram to use GSsl::Digester.
46 {
47 explicit DigesterAdaptor( const std::string & name ) :
48 m_name(name)
49 {
50 GSsl::Digester d( CramImp::lib().digester(m_name) ) ;
51 m_blocksize = d.blocksize() ;
52 }
53 std::string operator()( const std::string & data_1 , const std::string & data_2 ) const
54 {
55 GSsl::Digester d( CramImp::lib().digester(m_name) ) ;
56 d.add( data_1 ) ;
57 d.add( data_2 ) ;
58 return d.value() ;
59 }
60 std::size_t blocksize() const
61 {
62 return m_blocksize ;
63 }
64 std::string m_name ;
65 std::size_t m_blocksize ;
66 } ;
67 struct PostDigesterAdaptor /// Used by GAuth::Cram to use GSsl::Digester.
68 {
69 explicit PostDigesterAdaptor( const std::string & name ) :
70 m_name(name)
71 {
72 GSsl::Digester d( CramImp::lib().digester(m_name,std::string(),true) ) ;
73 if( d.statesize() == 0U )
74 throw GAuth::Cram::NoState( m_name ) ;
75 m_valuesize = d.valuesize() ;
76 m_blocksize = d.blocksize() ;
77 }
78 std::string operator()( const std::string & state_pair , const std::string & data ) const
79 {
80 if( state_pair.size() != (2U*m_valuesize) ) throw GAuth::Cram::InvalidState( m_name ) ;
81 std::string state_i = state_pair.substr( 0U , state_pair.size()/2U ) + G::HashStateImp::extension(m_blocksize) ;
82 std::string state_o = state_pair.substr( state_pair.size()/2U ) + G::HashStateImp::extension(m_blocksize) ;
83 GSsl::Digester xi( CramImp::lib().digester( m_name , state_i ) ) ;
84 xi.add( data ) ;
85 GSsl::Digester xo( CramImp::lib().digester( m_name , state_o ) ) ;
86 xo.add( xi.value() ) ;
87 return xo.value() ;
88 }
89 std::string m_name ;
90 std::size_t m_valuesize ;
91 std::size_t m_blocksize ;
92 } ;
93 }
94}
95
96std::string GAuth::Cram::response( const std::string & hash_type , bool as_hmac ,
97 const Secret & secret , const std::string & challenge ,
98 const std::string & id_prefix )
99{
100 try
101 {
102 G_DEBUG( "GAuth::Cram::response: [" << hash_type << "]"
103 << "[" << as_hmac << "]"
104 << "[" << G::Str::printable(secret.key()) << "]"
105 << "[" << secret.maskType() << "][" << challenge << "]"
106 << "[" << G::Str::printable(id_prefix) << "]"
107 << "[" << responseImp(hash_type,as_hmac,secret,challenge) << "]" ) ;
108
109 return id_prefix + " " + responseImp(hash_type,as_hmac,secret,challenge) ;
110 }
111 catch( std::exception & e )
112 {
113 G_WARNING( "GAuth::Cram::response: challenge-response failure: " << e.what() ) ;
114 return std::string() ;
115 }
116}
117
118 bool GAuth::Cram::validate( const std::string & hash_type , bool as_hmac ,
119 const Secret & secret , const std::string & challenge ,
120 const std::string & response_in )
121{
122 try
123 {
124 G_DEBUG( "GAuth::Cram::validate: [" << hash_type << "]"
125 << "[" << as_hmac << "]"
126 << "[" << G::Str::printable(secret.key()) << "]"
127 << "[" << secret.maskType() << "]"
128 << "[" << challenge << "]"
129 << "[" << response_in << "]"
130 << "[" << responseImp(hash_type,as_hmac,secret,challenge) << "]" ) ;
131
132 std::string expectation = G::Str::tail( response_in , response_in.rfind(' ') ) ;
133 return !expectation.empty() && responseImp(hash_type,as_hmac,secret,challenge) == expectation ;
134 }
135 catch( std::exception & e )
136 {
137 G_WARNING( "GAuth::Cram::validate: challenge-response failure: " << e.what() ) ;
138 return false ;
139 }
140}
141
142std::string GAuth::Cram::id( const std::string & response )
143{
144 // the response is "<id> <hexchars>" but also allow for ids with spaces
145 return G::Str::head( response , response.rfind(' ') ) ;
146}
147
148std::string GAuth::Cram::responseImp( const std::string & mechanism_hash_type , bool as_hmac ,
149 const Secret & secret , const std::string & challenge )
150{
151 G_DEBUG( "GAuth::Cram::responseImp: mechanism-hash=[" << mechanism_hash_type << "] "
152 << "secret-hash=[" << secret.maskType() << "] "
153 << "as-hmac=" << as_hmac ) ;
154
155 if( !as_hmac )
156 {
157 if( secret.masked() )
158 throw BadType( secret.maskType() ) ;
159
160 if( mechanism_hash_type == "MD5" )
161 {
162 return G::Hash::printable( G::Md5::digest(challenge,secret.key()) ) ;
163 }
164 else
165 {
166 CramImp::DigesterAdaptor digest( mechanism_hash_type ) ;
167 return G::Hash::printable( digest(challenge,secret.key()) ) ;
168 }
169 }
170 else if( secret.masked() )
171 {
172 if( ! G::Str::imatch(secret.maskType(),mechanism_hash_type) )
173 throw Mismatch( secret.maskType() , mechanism_hash_type ) ;
174
175 if( mechanism_hash_type == "MD5" )
176 {
177 return G::Hash::printable( G::Hash::hmac(G::Md5::postdigest,secret.key(),challenge,G::Hash::Masked()) ) ;
178 }
179 else
180 {
181 CramImp::PostDigesterAdaptor postdigest( mechanism_hash_type ) ;
182 return G::Hash::printable( G::Hash::hmac(postdigest,secret.key(),challenge,G::Hash::Masked()) ) ;
183 }
184 }
185 else
186 {
187 if( mechanism_hash_type == "MD5" )
188 {
189 return G::Hash::printable( G::Hash::hmac(G::Md5::digest2,G::Md5::blocksize(),secret.key(),challenge) ) ;
190 }
191 else
192 {
193 CramImp::DigesterAdaptor digest( mechanism_hash_type ) ;
194 return G::Hash::printable( G::Hash::hmac(digest,digest.blocksize(),secret.key(),challenge) ) ;
195 }
196 }
197}
198
199 G::StringArray GAuth::Cram::hashTypes( const std::string & prefix , bool require_state )
200{
201 // we can do CRAM-X for all hash functions (X) provided by the TLS library
202 // but if we only have masked passwords (ie. require_state) then we only
203 // want hash functions that are capable of initialision with intermediate state
204 //
205 G::StringArray result = GSsl::Library::digesters( require_state ) ; // strongest first
206 if( G::Test::enabled("cram-fake-hash") )
207 result.push_back( "FAKE" ) ;
208
209 G_DEBUG( "GAuth::Cram::hashTypes: tls library [" << GSsl::Library::ids() << "]" ) ;
210 G_DEBUG( "GAuth::Cram::hashTypes: tls library hash types: [" << G::Str::join(",",result) << "] "
211 << "(" << (require_state?1:0) << ")" ) ;
212
213 // always include MD5 since we use G::Md5 code
214 if( !G::Str::match( result , "MD5" ) )
215 result.push_back( "MD5" ) ;
216
217 if( !prefix.empty() )
218 {
219 for( auto & hashtype : result )
220 hashtype.insert( 0U , prefix ) ;
221 }
222 return result ;
223}
224
225std::string GAuth::Cram::challenge( unsigned int random )
226{
227 std::ostringstream ss ;
228 ss << "<" << random << "."
229 << G::SystemTime::now().s() << "@"
230 << GNet::Local::canonicalName() << ">" ;
231 return ss.str() ;
232}
233
GAuth::Cram::challenge
static std::string challenge(unsigned int random)
Returns a challenge string that incorporates the given random number and the current time.
Definition: gcram.cpp:225
GAuth::Cram::id
static std::string id(const std::string &response)
Returns the leading id part of the response.
Definition: gcram.cpp:142
GAuth::Cram::response
static std::string response(const std::string &hash_type, bool hmac, const Secret &secret, const std::string &challenge, const std::string &response_prefix)
Constructs a response to a challenge comprising the response-prefix, space, and digest-or-hmac of sec...
Definition: gcram.cpp:96
GAuth::Cram::validate
static bool validate(const std::string &hash_type, bool hmac, const Secret &secret, const std::string &challenge, const std::string &response)
Validates the response with respect to the original challenge.
Definition: gcram.cpp:118
GAuth::Cram::hashTypes
static G::StringArray hashTypes(const std::string &prefix=std::string(), bool require_state=false)
Returns a list of supported hash types, such as "MD5" and "SHA1", ordered with the strongest first.
Definition: gcram.cpp:199
GAuth::Secret
Encapsulates a shared secret from the secrets file plus the associated userid.
Definition: gsecret.h:42
GAuth::Secret::masked
bool masked() const
Returns true if key() is masked.
Definition: gsecret.cpp:110
GAuth::Secret::maskType
std::string maskType() const
Returns the masking function name, such as "MD5", or the empty string if not masked().
Definition: gsecret.cpp:121
GAuth::Secret::key
std::string key() const
Returns the key. Throws if not valid().
Definition: gsecret.cpp:104
GNet::Local::canonicalName
static std::string canonicalName()
Returns the canonical network name assiciated with hostname().
Definition: glocal.cpp:53
GSsl::Digester
A class for objects that can perform a cryptographic hash.
Definition: gssl.h:213
GSsl::Digester::valuesize
std::size_t valuesize() const
Returns the hash function's value size in bytes.
Definition: gssl.cpp:245
GSsl::Digester::statesize
std::size_t statesize() const
Returns the size of the state() string in bytes, or zero if state() is not implemented.
Definition: gssl.cpp:250
GSsl::Digester::value
std::string value()
Returns the hash value.
Definition: gssl.cpp:230
GSsl::Digester::add
void add(const std::string &)
Adds data of arbitrary size.
Definition: gssl.cpp:225
GSsl::Digester::blocksize
std::size_t blocksize() const
Returns the hash function's block size in bytes.
Definition: gssl.cpp:240
GSsl::Library
A singleton class for initialising the underlying TLS library.
Definition: gssl.h:253
GSsl::Library::instance
static Library * instance()
Returns a pointer to a library object, if any.
Definition: gssl.cpp:56
GSsl::Library::ids
static std::string ids()
Returns a concatenation of all available TLS library names and versions.
Definition: gssl_use_openssl.cpp:35
GSsl::Library::digesters
static G::StringArray digesters(bool need_state=false)
Returns a list of hash function names (such as "MD5") that the TLS library can do,...
Definition: gssl.cpp:139
G::Exception
A general-purpose exception class derived from std::exception and containing an error message.
Definition: gexception.h:45
G::HashStateImp::extension
static std::string extension(U n)
Returns the given data size as a four-character string.
Definition: ghashstate.h:161
G::Hash::hmac
static std::string hmac(Fn2 digest, std::size_t blocksize, const std::string &key, const std::string &input)
Computes a Hashed Message Authentication Code using the given hash function.
Definition: ghash.h:120
G::Hash::printable
static std::string printable(const std::string &input)
Converts a binary string into a printable form, using a lowercase hexadecimal encoding.
Definition: ghash.cpp:53
G::Md5::postdigest
static std::string postdigest(const std::string &state_pair, const std::string &message)
A convenience function that returns the value() from an outer digest that is initialised with the sec...
Definition: gmd5.cpp:562
G::Md5::blocksize
static std::size_t blocksize()
Returns the block size in bytes (64).
Definition: gmd5.cpp:580
G::Md5::digest2
static std::string digest2(const std::string &input_1, const std::string &input_2)
A non-overloaded name for the digest() overload taking two parameters.
Definition: gmd5.cpp:549
G::Md5::digest
static std::string digest(const std::string &input)
A convenience function that returns a digest from one input.
Definition: gmd5.cpp:535
G::Str::join
static std::string join(const std::string &sep, const StringArray &strings)
Concatenates an array of strings with separators.
Definition: gstr.cpp:1195
G::Str::imatch
static bool imatch(char, char)
Returns true if the two characters are the same, ignoring Latin-1 case.
Definition: gstr.cpp:1414
G::Str::tail
static std::string tail(const std::string &in, std::size_t pos, const std::string &default_=std::string())
Returns the last part of the string after the given position.
Definition: gstr.cpp:1287
G::Str::match
static bool match(const std::string &, const std::string &)
Returns true if the two strings are the same.
Definition: gstr.cpp:1368
G::Str::printable
static std::string printable(const std::string &in, char escape='\\')
Returns a printable representation of the given input string, using chacter code ranges 0x20 to 0x7e ...
Definition: gstr.cpp:885
G::Str::head
static std::string head(const std::string &in, std::size_t pos, const std::string &default_=std::string())
Returns the first part of the string up to just before the given position.
Definition: gstr.cpp:1273
G::SystemTime::now
static SystemTime now()
Factory function for the current time.
Definition: gdatetime.cpp:260
G::SystemTime::s
std::time_t s() const noexcept
Returns the number of seconds since the start of the epoch.
Definition: gdatetime.cpp:308
G::Test::enabled
static bool enabled() noexcept
Returns true if test features are enabled.
Definition: gtest.cpp:79
gbase64.h
gcram.h
gexception.h
ghash.h
ghashstate.h
glocal.h
glog.h
gmd5.h
gssl.h
An interface to an underlying TLS library.
gstr.h
gtest.h
GAuth
SASL authentication classes.
Definition: gcram.cpp:36
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31
GAuth::CramImp::DigesterAdaptor
Used by GAuth::Cram to use GSsl::Digester.
Definition: gcram.cpp:46
GAuth::CramImp::PostDigesterAdaptor
Used by GAuth::Cram to use GSsl::Digester.
Definition: gcram.cpp:68
G::Hash::Masked
An overload discriminator for G::Hash::hmac()
Definition: ghash.h:41