From b211df3a0cca478822d57836dac3dd6452aab32a Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Tue, 18 Oct 2011 18:01:40 +0200 Subject: Additions to crypto and public_key needed for full PKCS-8 support --- lib/public_key/src/pubkey_pbe.erl | 105 +++++++++++++++++++++++--------------- lib/public_key/src/public_key.erl | 1 - lib/public_key/test/pbe_SUITE.erl | 60 ++++++++++++---------- 3 files changed, 97 insertions(+), 69 deletions(-) (limited to 'lib/public_key') diff --git a/lib/public_key/src/pubkey_pbe.erl b/lib/public_key/src/pubkey_pbe.erl index f471871d35..32be347039 100644 --- a/lib/public_key/src/pubkey_pbe.erl +++ b/lib/public_key/src/pubkey_pbe.erl @@ -27,35 +27,43 @@ -define(DEFAULT_SHA_MAC_KEYLEN, 20). --define(OCTET_STR, 4). --define(IV_LEN, 8). +-define(ASN1_OCTET_STR_TAG, 4). +-define(IV_LEN, 8). %%==================================================================== %% Internal application API %%==================================================================== -pbdkdf2(Password, Salt, Count, DerivedKeyLen, PrfLen, Prf)-> - NumBlocks = ceiling(DerivedKeyLen / PrfLen), - NumLastBlockOctets = DerivedKeyLen - (NumBlocks - 1) * PrfLen , - blocks(NumBlocks, NumLastBlockOctets, 1, Password, Salt, Count, Prf, PrfLen, <<>>). +pbdkdf2(Password, Salt, Count, DerivedKeyLen, Prf, PrfOutputLen)-> + NumBlocks = ceiling(DerivedKeyLen / PrfOutputLen), + NumLastBlockOctets = DerivedKeyLen - (NumBlocks - 1) * PrfOutputLen , + blocks(NumBlocks, NumLastBlockOctets, 1, Password, Salt, Count, Prf, PrfOutputLen, <<>>). encode(Data, Password, "DES-CBC" = Cipher, KeyDevParams) -> - {Key, IV} = password_to_key_and_iv(Password, derived_key_length(Cipher), KeyDevParams), + {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams), crypto:des_cbc_encrypt(Key, IV, Data); encode(Data, Password, "DES-EDE3-CBC" = Cipher, KeyDevParams) -> - {Key, IV} = password_to_key_and_iv(Password, derived_key_length(Cipher), KeyDevParams), + {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams), <> = Key, - crypto:des_ede3_cbc_encrypt(Key1, Key2, Key3, IV, Data). + crypto:des_ede3_cbc_encrypt(Key1, Key2, Key3, IV, Data); + +encode(Data, Password, "RC2-CBC" = Cipher, KeyDevParams) -> + {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams), + crypto:rc2_cbc_encrypt(Key, IV, Data). decode(Data, Password,"DES-CBC"= Cipher, KeyDevParams) -> - {Key, IV} = password_to_key_and_iv(Password, derived_key_length(Cipher), KeyDevParams), + {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams), crypto:des_cbc_decrypt(Key, IV, Data); decode(Data, Password,"DES-EDE3-CBC" = Cipher, KeyDevParams) -> - {Key, IV} = password_to_key_and_iv(Password, derived_key_length(Cipher), KeyDevParams), + {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams), <> = Key, - crypto:des_ede3_cbc_decrypt(Key1, Key2, Key3, IV, Data). + crypto:des_ede3_cbc_decrypt(Key1, Key2, Key3, IV, Data); + +decode(Data, Password,"RC2-CBC"= Cipher, KeyDevParams) -> + {Key, IV} = password_to_key_and_iv(Password, Cipher, KeyDevParams), + crypto:rc2_cbc_decrypt(Key, IV, Data). %%-------------------------------------------------------------------- -spec pbdkdf1(string(), iodata(), integer(), atom()) -> binary(). @@ -94,13 +102,20 @@ decrypt_parameters(#'EncryptedPrivateKeyInfo_encryptionAlgorithm'{ %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -password_to_key_and_iv(Password, KeyLen, {salt, Salt}) -> +password_to_key_and_iv(Password, Cipher, {salt, Salt}) -> + KeyLen = derived_key_length(Cipher, undefined), <> = pem_encrypt(<<>>, Password, Salt, ceiling(KeyLen div 16), <<>>, md5), %% Old PEM encryption does not use standard encryption method %% pbdkdf1 and uses then salt as IV {Key, Salt}. +password_to_key_and_iv(Password, _, #'PBES2-params'{} = Params) -> + {Salt, ItrCount, KeyLen, PseudoRandomFunction, PseudoOtputLen, IV} = + key_derivation_params(Params), + <> = + pbdkdf2(Password, Salt, ItrCount, KeyLen, PseudoRandomFunction, PseudoOtputLen), + {Key, IV}. pem_encrypt(_, _, _, 0, Acc, _) -> Acc; pem_encrypt(Prev, Password, Salt, Count, Acc, Hash) -> @@ -118,8 +133,12 @@ iv(#'PBES2-params_encryptionScheme'{algorithm = Algo, (Algo == ?'des-EDE3-CBC') -> %% This is an so called open ASN1-type that in this %% case will be an octet-string of length 8 - <> = ASNIV, - IV. + <> = ASNIV, + IV; +iv(#'PBES2-params_encryptionScheme'{algorithm = ?'rc2CBC', + parameters = ASN1IV}) -> + {ok, #'RC2-CBC-Parameter'{iv = IV}} = 'PKCS-FRAME':decode('RC2-CBC-Parameter', ASN1IV), + iolist_to_binary(IV). blocks(1, N, Index, Password, Salt, Count, Prf, PrfLen, Acc) -> <> = xor_sum(Password, Salt, Count, Index, Prf, PrfLen), @@ -129,53 +148,55 @@ blocks(NumBlocks, N, Index, Password, Salt, Count, Prf, PrfLen, Acc) -> blocks(NumBlocks -1, N, Index +1, Password, Salt, Count, Prf, PrfLen, <>). xor_sum(Password, Salt, Count, Index, Prf, PrfLen) -> - %%Result = Prf(Password, [Salt,<>], PrfLen), - Result = Prf(Password, [Salt,<>]), + Result = Prf(Password, [Salt,<>], PrfLen), do_xor_sum(Prf, PrfLen, Result, Password, Count-1, Result). do_xor_sum(_, _, _, _, 0, Acc) -> Acc; do_xor_sum(Prf, PrfLen, Prev, Password, Count, Acc)-> - %%Result = Prf(Password, Prev, PrfLen), - Result = Prf(Password, Prev), + Result = Prf(Password, Prev, PrfLen), do_xor_sum(Prf, PrfLen, Result, Password, Count-1, crypto:exor(Acc, Result)). decrypt_parameters(?'id-PBES2', DekParams) -> {ok, Params} = 'PKCS-FRAME':decode('PBES2-params', DekParams), {cipher(Params#'PBES2-params'.encryptionScheme), Params}. -key_derivation_params(#'PBES2-params_keyDerivationFunc'{algorithm = ?'id-PBKDF2', - parameters = - #'PBKDF2-params'{salt = {specified, OctetSalt}, - iterationCount = Count, - keyLength = Length, - prf = Prf}}) -> - PseudoRandomFunction = pseudo_random_function(Prf), - KeyLen = pseudo_key_length(Length, Prf), - {iolist_to_binary(OctetSalt), Count, KeyLen, PseudoRandomFunction}. - +key_derivation_params(#'PBES2-params'{keyDerivationFunc = KeyDerivationFunc, + encryptionScheme = EncScheme}) -> + #'PBES2-params_keyDerivationFunc'{algorithm = ?'id-PBKDF2', + parameters = + #'PBKDF2-params'{salt = {specified, OctetSalt}, + iterationCount = Count, + keyLength = Length, + prf = Prf}} = KeyDerivationFunc, + #'PBES2-params_encryptionScheme'{algorithm = Algo} = EncScheme, + {PseudoRandomFunction, PseudoOtputLen} = pseudo_random_function(Prf), + KeyLen = derived_key_length(Algo, Length), + {iolist_to_binary(OctetSalt), Count, KeyLen, PseudoRandomFunction, PseudoOtputLen, iv(EncScheme)}. + +%% This function currently matches a tuple that ougth to be the value ?'id-hmacWithSHA1, +%% but we need some kind of ASN1-fix for this. pseudo_random_function(#'PBKDF2-params_prf'{algorithm = {_,_, _,'id-hmacWithSHA1'}}) -> - %%fun crypto:sha_mac_n/3. - fun crypto:sha_mac/2. + {fun crypto:sha_mac/3, pseudo_output_length(?'id-hmacWithSHA1')}. -pseudo_key_length(asn1_NOVALUE, #'PBKDF2-params_prf'{algorithm = {_,_, _,'id-hmacWithSHA1'}}) -> - ?DEFAULT_SHA_MAC_KEYLEN; -pseudo_key_length(Len, _) when is_integer(Len) -> - Len. +pseudo_output_length(?'id-hmacWithSHA1') -> + ?DEFAULT_SHA_MAC_KEYLEN. -derived_key_length("DES-CBC") -> +derived_key_length(_, Len) when is_integer(Len) -> + Len; +derived_key_length(Cipher,_) when (Cipher == ?'desCBC') or (Cipher == "DES-CBC") -> 8; -%% derived_key_length("RC2-CBC") -> -%% 5; -derived_key_length("DES-EDE3-CBC") -> +derived_key_length(Cipher,_) when (Cipher == ?'rc2CBC') or (Cipher == "RC2-CBC") -> + 16; +derived_key_length(Cipher,_) when (Cipher == ?'des-EDE3-CBC') or (Cipher == "DES-EDE3-CBC") -> 24. cipher(#'PBES2-params_encryptionScheme'{algorithm = ?'desCBC'}) -> "DES-CBC"; cipher(#'PBES2-params_encryptionScheme'{algorithm = ?'des-EDE3-CBC'}) -> - "DES-EDE3-CBC". -%% cipher(#'PBES2-params_encryptionScheme'{algorithm = ?'rc2CBC'}) -> -%% "RC2-CBC". + "DES-EDE3-CBC"; +cipher(#'PBES2-params_encryptionScheme'{algorithm = ?'rc2CBC'}) -> + "RC2-CBC". ceiling(Float) -> erlang:round(Float + 0.5). diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index 68c7b7ad93..19465e7828 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -154,7 +154,6 @@ der_decode(Asn1Type, Der) when (Asn1Type == 'PrivateKeyInfo') or (Asn1Type == ' andalso is_binary(Der) -> try {ok, Decoded} = 'PKCS-FRAME':decode(Asn1Type, Der), - Decoded catch error:{badmatch, {error, _}} = Error -> diff --git a/lib/public_key/test/pbe_SUITE.erl b/lib/public_key/test/pbe_SUITE.erl index 1d33976505..8dc9a01529 100644 --- a/lib/public_key/test/pbe_SUITE.erl +++ b/lib/public_key/test/pbe_SUITE.erl @@ -20,6 +20,7 @@ -module(pbe_SUITE). -include_lib("test_server/include/test_server.hrl"). +-include_lib("public_key/include/public_key.hrl"). %% Note: This directive should only be used in test suites. -compile(export_all). @@ -157,7 +158,7 @@ pbdkdf2(Config) when is_list(Config) -> <<16#ea, 16#6c, 16#01, 16#4d, 16#c7, 16#2d, 16#6f, 16#8c, 16#cd, 16#1e, 16#d9, 16#2a, 16#ce, 16#1d, 16#41, 16#f0, 16#d8, 16#de, 16#89, 16#57>> = - pubkey_pbe:pbdkdf2("password", "salt", 2, 20, 20, fun crypto:sha_mac/2), + pubkey_pbe:pbdkdf2("password", "salt", 2, 20, fun crypto:sha_mac/3, 20), %% Input: %% P = "password" (8 octets) @@ -172,7 +173,7 @@ pbdkdf2(Config) when is_list(Config) -> <<16#4b, 16#00, 16#79, 16#01, 16#b7, 16#65, 16#48, 16#9a, 16#be, 16#ad, 16#49, 16#d9, 16#26, 16#f7, 16#21, 16#d0, - 16#65, 16#a4, 16#29, 16#c1>> = pubkey_pbe:pbdkdf2("password", "salt", 4096, 20, 20, fun crypto:sha_mac/2), + 16#65, 16#a4, 16#29, 16#c1>> = pubkey_pbe:pbdkdf2("password", "salt", 4096, 20, fun crypto:sha_mac/3, 20), %% Input: %% P = "password" (8 octets) @@ -188,7 +189,7 @@ pbdkdf2(Config) when is_list(Config) -> <<16#ee, 16#fe, 16#3d, 16#61, 16#cd, 16#4d, 16#a4, 16#e4, 16#e9, 16#94, 16#5b, 16#3d, 16#6b, 16#a2, 16#15, 16#8c, - 16#26, 16#34, 16#e9, 16#84>> = pubkey_pbe:pbdkdf2("password", "salt", 16777216, 20, 20, fun crypto:sha_mac/2), + 16#26, 16#34, 16#e9, 16#84>> = pubkey_pbe:pbdkdf2("password", "salt", 16777216, 20, fun crypto:sha_mac/3, 20), %% Input: %% P = "passwordPASSWORDpassword" (24 octets) @@ -207,7 +208,7 @@ pbdkdf2(Config) when is_list(Config) -> 16#8b, 16#29, 16#1a, 16#96, 16#4c, 16#f2, 16#f0, 16#70, 16#38>> = pubkey_pbe:pbdkdf2("passwordPASSWORDpassword", - "saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096, 25, 20, fun crypto:sha_mac/2), + "saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096, 25, fun crypto:sha_mac/3, 20), %% Input: %% P = "pass\0word" (9 octets) @@ -222,30 +223,37 @@ pbdkdf2(Config) when is_list(Config) -> <<16#56, 16#fa, 16#6a, 16#a7, 16#55, 16#48, 16#09, 16#9d, 16#cc, 16#37, 16#d7, 16#f0, 16#34, 16#25, 16#e0, 16#c3>> = pubkey_pbe:pbdkdf2("pass\0word", - "sa\0lt", 4096, 16, 20, fun crypto:sha_mac/2). + "sa\0lt", 4096, 16, fun crypto:sha_mac/3, 20). - -pbe_des_cbc(doc) -> - ["Tests reading a password DES-CBC encrypted key file"]; -pbe_des_cbc(Config) when is_list(Config) -> +encrypted_private_key_info(doc) -> + ["Tests reading a EncryptedPrivateKeyInfo file different ciphers"]; +encrypted_private_key_info(Config) when is_list(Config) -> Datadir = ?config(data_dir, Config), - {ok, Pem} = file:read_file(filename:join(Datadir, "des_cbc_enc_key.pem")), + {ok, PemDes} = file:read_file(filename:join(Datadir, "des_cbc_enc_key.pem")), + PemDesEntry = public_key:pem_decode(PemDes), + test_server:format("Pem entry: ~p" , [PemDesEntry]), + [{'PrivateKeyInfo', _, {"DES-CBC",_}} = PubEntry0] = PemDesEntry, + KeyInfo = public_key:pem_entry_decode(PubEntry0, "password"), - PemE = public_key:pem_decode(Pem), - test_server:format("PemE: ~p" , [PemE]), - [{'PrivateKeyInfo', _, _} = PubEntry0] = PemE, - Key = public_key:pem_entry_decode(PubEntry0, "password"), - test_server:format("Key: ~p" , [Key]). - -pbe_des3_ede(doc) -> - ["Tests reading a password DES-CBC encrypted key file"]; -pbe_des3_ede(Config) when is_list(Config) -> - Datadir = ?config(data_dir, Config), - {ok, Pem} = file:read_file(filename:join(Datadir, "des_ede3_cbc_enc_key.pem")), + {ok, Pem3Des} = file:read_file(filename:join(Datadir, "des_ede3_cbc_enc_key.pem")), + + Pem3DesEntry = public_key:pem_decode(Pem3Des), + test_server:format("Pem entry: ~p" , [Pem3DesEntry]), + [{'PrivateKeyInfo', _, {"DES-EDE3-CBC",_}} = PubEntry1] = Pem3DesEntry, + KeyInfo = public_key:pem_entry_decode(PubEntry1, "password"), + + {ok, PemRc2} = file:read_file(filename:join(Datadir, "rc2_cbc_enc_key.pem")), + + PemRc2Entry = public_key:pem_decode(PemRc2), + test_server:format("Pem entry: ~p" , [PemRc2Entry]), + [{'PrivateKeyInfo', _, {"RC2-CBC",_}} = PubEntry2] = PemRc2Entry, + KeyInfo = public_key:pem_entry_decode(PubEntry2, "password"), + + check_key_info(KeyInfo). + - PemE = public_key:pem_decode(Pem), - test_server:format("PemE: ~p" , [PemE]), - [{'PrivateKeyInfo', _, _} = PubEntry0] = PemE, - Key = public_key:pem_entry_decode(PubEntry0, "password"), - test_server:format("Key: ~p" , [Key]). +check_key_info(#'PrivateKeyInfo'{privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?rsaEncryption}, + privateKey = Key}) -> + #'RSAPrivateKey'{} = public_key:der_decode('RSAPrivateKey', iolist_to_binary(Key)). -- cgit v1.2.3