%%%============================================================================= %%% Copyright (c) 2012 Lindenbaum GmbH %%% %%% Permission to use, copy, modify, and/or distribute this software for any %%% purpose with or without fee is hereby granted, provided that the above %%% copyright notice and this permission notice appear in all copies. %%% %%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. %%% %%% @doc %%% A module providing the supported authentication algorithms for IPMI packets. %%% Currently supported algorithms are MD2, MD5, straight password, and those %%% specified in RMCP+. %%% @end %%%============================================================================= -module(eipmi_auth). -export([ encrypt/3, decrypt/3, encode_encrypt_type/1, encode_integrity_type/1, encode_payload_type/1, encode_rakp_type/1, encode_type/1, decode_encrypt_type/1, decode_integrity_type/1, decode_payload_type/1, decode_rakp_type/1, decode_type/1, hash/2, hash/3, extra_key/3, rakp_hash/4 ]). -type encrypt_type() :: none | aes_cbc. -type integrity_type() :: none | hmac_sha1_96 | hmac_md5_128 | md5_128 | hmac_sha256_128. -type payload_type() :: ipmi | open_session_rq | open_session_rs | rakp1 | rakp2 | rakp3 | rakp4. -type rakp_type() :: none | hmac_sha1 | hmac_md5 | hmac_sha256. -type type() :: none | md2 | md5 | pwd. -export_type([ encrypt_type/0, integrity_type/0, payload_type/0, rakp_type/0, type/0 ]). %%%============================================================================= %%% API %%%============================================================================= %%------------------------------------------------------------------------------ %% @doc %% Hashes a given binary according to the requested authentication %% algorithm. The returned cipher is always a binary of 16bytes or an empty %% binary for authentication code `none'. Straight passwords are either padded %% or cut to a length of 16bytes. %% @end %%------------------------------------------------------------------------------ -spec hash(type(), binary() | string()) -> binary(). hash(none, _Ignored) -> <<>>; hash(md2, Binary) -> md2:hash(Binary); hash(md5, Binary) -> crypto:hash(md5, Binary); hash(pwd, Password) -> eipmi_util:normalize(16, Password). -spec hash(integrity_type(), iodata(), binary()) -> binary(). hash(none, _Key, _Ignored) -> <<>>; hash(md5_128, K, Binary) -> Key = iolist_to_binary(K), crypto:hash(md5, <>); hash(hmac_sha1_96, K, B) -> crypto:macN(hmac, sha, K, B, 12); hash(hmac_md5_128, K, B) -> crypto:macN(hmac, md5, K, B, 16); hash(hmac_sha256_128, K, B) -> crypto:macN(hmac, sha256, K, B, 16). -spec extra_key( encrypt_type() | 1..255, rakp_type() | integrity_type(), binary() ) -> binary(). extra_key(none, _Ht, _Sk) -> <<>>; extra_key(aes_cbc, Ht, Sk) -> binary_part(extra_key(2, Ht, Sk), {0, 16}); extra_key(N, HashType, SessionKey) -> Algo = hash_algo(HashType), % NB: A plain reading of the IPMI spec would indicate that % the constants used to generate extra keying material % should be as large as the block size of the hash % algorithm. The creators of `ipmitool` thought the spec % only indicated constants of a 20-byte length. As ipmitool % works with the hardware I have tested against, I'm going % to consider them correct. -JLM Const = binary:copy(<>, 20), crypto:mac(hmac, Algo, SessionKey, Const). -spec hash_algo(rakp_type() | integrity_type()) -> md5 | sha | sha256. hash_algo(hmac_md5) -> md5; hash_algo(hmac_md5_128) -> md5; hash_algo(hmac_sha1) -> sha; hash_algo(hmac_sha1_96) -> sha; hash_algo(hmac_sha256) -> sha256; hash_algo(hmac_sha256_128) -> sha256. -spec rakp_hash(rakp_type(), payload_type(), binary(), binary()) -> binary(). rakp_hash(hmac_md5, _, Key, Bin) -> hash(hmac_md5_128, Key, Bin); rakp_hash(hmac_sha1, rakp4, Key, Bin) -> crypto:macN(hmac, sha, Key, Bin, 12); rakp_hash(hmac_sha1, _, Key, Bin) -> crypto:mac(hmac, sha, Key, Bin); rakp_hash(hmac_sha256, rakp4, Key, Bin) -> crypto:macN(hmac, sha256, Key, Bin, 16); rakp_hash(hmac_sha256, _, Key, Bin) -> crypto:mac(hmac, sha256, Key, Bin). %%------------------------------------------------------------------------------- %% @doc %% Encrypts a given binary according to the requested confidentiality %% algorithm. Returns a binary containing the Confidentiality Header and the %% encrypted Payload + Confidentiality Trailer. %%------------------------------------------------------------------------------- -spec encrypt(encrypt_type(), binary(), binary()) -> binary(). encrypt(none, _Key, Binary) -> Binary; encrypt(aes_cbc, Key, Binary) -> Iv = crypto:strong_rand_bytes(16), % IPMI uses a different padding scheme than the % crypto library: pad to a multiple of 16 bytes, % less 1 to encode the padding length; the padding % bytes are sequential numbers starting from 1. PadLength = 15 - size(Binary) rem 16, Padding = list_to_binary(lists:seq(1, PadLength)), ToEncrypt = <>, Encrypted = crypto_one_time_aes_128_cbc(Key, Iv, ToEncrypt, true), <>. %%------------------------------------------------------------------------------- %% @doc %% Decrypts a given binary according to the requested confidentiality %% algorithm. %%------------------------------------------------------------------------------- -spec decrypt(encrypt_type(), binary(), binary()) -> binary(). decrypt(none, _Key, Binary) -> Binary; decrypt(aes_cbc, Key, <>) -> Result = crypto_one_time_aes_128_cbc(Key, Iv, Encrypted, false), PadLength = binary:last(Result), % PadLength does not count its own byte. binary_part(Result, {0, size(Result) - 1 - PadLength}). %%------------------------------------------------------------------------------ %% @doc %% Encodes an encryption algorithm into its integer representation. %% @end %%------------------------------------------------------------------------------ encode_encrypt_type(none) -> 0; encode_encrypt_type(aes_cbc) -> 1. %%------------------------------------------------------------------------------ %% @doc %% Encodes an integrity algorithm into its integer representation. %% @end %%------------------------------------------------------------------------------ -spec encode_integrity_type(integrity_type()) -> 0..4. encode_integrity_type(none) -> 0; encode_integrity_type(hmac_sha1_96) -> 1; encode_integrity_type(hmac_md5_128) -> 2; encode_integrity_type(md5_128) -> 3; encode_integrity_type(hmac_sha256_128) -> 4. %%------------------------------------------------------------------------------ %% @doc %% Encodes a payload type into its integer representation. %% @end %%------------------------------------------------------------------------------ -spec encode_payload_type(payload_type()) -> 0 | 16..21. encode_payload_type(ipmi) -> 0; encode_payload_type(open_session_rq) -> 16#10; encode_payload_type(open_session_rs) -> 16#11; encode_payload_type(rakp1) -> 16#12; encode_payload_type(rakp2) -> 16#13; encode_payload_type(rakp3) -> 16#14; encode_payload_type(rakp4) -> 16#15. %%------------------------------------------------------------------------------- %% @doc %% Encodes a RAKP algorithm into its integer representation. %% @end %%------------------------------------------------------------------------------- -spec encode_rakp_type(rakp_type()) -> 0..3. encode_rakp_type(none) -> 0; encode_rakp_type(hmac_sha1) -> 1; encode_rakp_type(hmac_md5) -> 2; encode_rakp_type(hmac_sha256) -> 3. %%------------------------------------------------------------------------------ %% @doc %% Encodes an authentication type into its integer representation. %% @end %%------------------------------------------------------------------------------ -spec encode_type(type() | rmcp_plus) -> 0..4 | 6. encode_type(none) -> 0; encode_type(md2) -> 1; encode_type(md5) -> 2; encode_type(pwd) -> 4; encode_type(rmcp_plus) -> 6. %%------------------------------------------------------------------------------ %% @doc %% Decodes an encryption algorithm into human readable format. %% @end %%------------------------------------------------------------------------------ decode_encrypt_type(0) -> none; decode_encrypt_type(1) -> aes_cbc. %%------------------------------------------------------------------------------ %% @doc %% Decodes an integrity algorithm integer into human readable format. %% @end %%------------------------------------------------------------------------------ -spec decode_integrity_type(0..4) -> integrity_type(). decode_integrity_type(0) -> none; decode_integrity_type(1) -> hmac_sha1_96; decode_integrity_type(2) -> hmac_md5_128; decode_integrity_type(3) -> md5_128; decode_integrity_type(4) -> hmac_sha256_128. %%------------------------------------------------------------------------------ %% @doc %% Decodes a payload type integer into human readable format. %% @end %%------------------------------------------------------------------------------ -spec decode_payload_type(0 | 16..21) -> payload_type(). decode_payload_type(0) -> ipmi; decode_payload_type(16#10) -> open_session_rq; decode_payload_type(16#11) -> open_session_rs; decode_payload_type(16#12) -> rakp1; decode_payload_type(16#13) -> rakp2; decode_payload_type(16#14) -> rakp3; decode_payload_type(16#15) -> rakp4. %%------------------------------------------------------------------------------- %% @doc %% Decodes a RAKP algorithm integer into human readable format. %% @end %%------------------------------------------------------------------------------- -spec decode_rakp_type(0..3) -> rakp_type(). decode_rakp_type(0) -> none; decode_rakp_type(1) -> hmac_sha1; decode_rakp_type(2) -> hmac_md5; decode_rakp_type(3) -> hmac_sha256. %%------------------------------------------------------------------------------ %% @doc %% Decodes an authentication type integer into human readable format. %% @end %%------------------------------------------------------------------------------ -spec decode_type(0..4 | 6) -> type() | rmcp_plus. decode_type(0) -> none; decode_type(1) -> md2; decode_type(2) -> md5; decode_type(4) -> pwd; decode_type(6) -> rmcp_plus. %%%============================================================================= %%% Internal functions %%%============================================================================= %%------------------------------------------------------------------------------ %% @private %%------------------------------------------------------------------------------ -if(?OTP_RELEASE < 23). crypto_one_time_aes_128_cbc(Key, IV, Data, EncryptFlag) -> crypto:crypto_one_time( aes_128_cbc, Key, IV, Data, EncryptFlag ). -else. crypto_one_time_aes_128_cbc(Key, IV, Data, EncryptFlag) -> crypto:crypto_one_time( aes_128_cbc, Key, IV, Data, [{encrypt, EncryptFlag}, {padding, none}] ). -endif.