class JSON::JWE
Constants
- NUM_OF_SEGMENTS
Attributes
auth_data[RW]
authentication_tag[W]
cipher_text[RW]
content_encryption_key[RW]
encryption_key[RW]
iv[RW]
jwe_encrypted_key[W]
mac_key[RW]
plain_text[RW]
private_key_or_secret[RW]
public_key_or_secret[RW]
Public Class Methods
decode_compact_serialized(input, private_key_or_secret, algorithms = nil, encryption_methods = nil, _allow_blank_payload = false)
click to toggle source
# File lib/json/jwe.rb, line 262 def decode_compact_serialized(input, private_key_or_secret, algorithms = nil, encryption_methods = nil, _allow_blank_payload = false) unless input.count('.') + 1 == NUM_OF_SEGMENTS raise InvalidFormat.new("Invalid JWE Format. JWE should include #{NUM_OF_SEGMENTS} segments.") end jwe = new _header_json_, jwe.jwe_encrypted_key, jwe.iv, jwe.cipher_text, jwe.authentication_tag = input.split('.', NUM_OF_SEGMENTS).collect do |segment| begin Base64.urlsafe_decode64 segment rescue ArgumentError raise DecryptionFailed end end jwe.auth_data = input.split('.').first jwe.header = JSON.parse(_header_json_).with_indifferent_access unless private_key_or_secret == :skip_decryption jwe.decrypt! private_key_or_secret, algorithms, encryption_methods end jwe end
decode_json_serialized(input, private_key_or_secret, algorithms = nil, encryption_methods = nil, _allow_blank_payload = false)
click to toggle source
# File lib/json/jwe.rb, line 282 def decode_json_serialized(input, private_key_or_secret, algorithms = nil, encryption_methods = nil, _allow_blank_payload = false) input = input.with_indifferent_access jwe_encrypted_key = if input[:recipients].present? input[:recipients].first[:encrypted_key] else input[:encrypted_key] end compact_serialized = [ input[:protected], jwe_encrypted_key, input[:iv], input[:ciphertext], input[:tag] ].join('.') decode_compact_serialized compact_serialized, private_key_or_secret, algorithms, encryption_methods end
new(input = nil)
click to toggle source
# File lib/json/jwe.rb, line 25 def initialize(input = nil) self.plain_text = input.to_s end
Public Instance Methods
as_json(options = {})
click to toggle source
# File lib/json/jwe.rb, line 84 def as_json(options = {}) case options[:syntax] when :general { protected: Base64.urlsafe_encode64(header.to_json, padding: false), recipients: [{ encrypted_key: Base64.urlsafe_encode64(jwe_encrypted_key, padding: false) }], iv: Base64.urlsafe_encode64(iv, padding: false), ciphertext: Base64.urlsafe_encode64(cipher_text, padding: false), tag: Base64.urlsafe_encode64(authentication_tag, padding: false) } else { protected: Base64.urlsafe_encode64(header.to_json, padding: false), encrypted_key: Base64.urlsafe_encode64(jwe_encrypted_key, padding: false), iv: Base64.urlsafe_encode64(iv, padding: false), ciphertext: Base64.urlsafe_encode64(cipher_text, padding: false), tag: Base64.urlsafe_encode64(authentication_tag, padding: false) } end end
decrypt!(private_key_or_secret, algorithms = nil, encryption_methods = nil)
click to toggle source
# File lib/json/jwe.rb, line 42 def decrypt!(private_key_or_secret, algorithms = nil, encryption_methods = nil) raise UnexpectedAlgorithm.new('Unexpected alg header') unless algorithms.blank? || Array(algorithms).include?(alg) raise UnexpectedAlgorithm.new('Unexpected enc header') unless encryption_methods.blank? || Array(encryption_methods).include?(enc) self.private_key_or_secret = with_jwk_support private_key_or_secret self.content_encryption_key = decrypt_content_encryption_key self.mac_key, self.encryption_key = derive_encryption_and_mac_keys verify_cbc_authentication_tag! if cbc? cipher.decrypt cipher.key = encryption_key cipher.iv = iv # NOTE: 'iv' has to be set after 'key' for GCM if gcm? # https://github.com/ruby/openssl/issues/63 raise DecryptionFailed.new('Invalid authentication tag') if authentication_tag.length < 16 cipher.auth_tag = authentication_tag cipher.auth_data = auth_data end begin self.plain_text = cipher.update(cipher_text) + cipher.final rescue OpenSSL::OpenSSLError # Ensure that the same error is raised for invalid PKCS7 padding # as for invalid signatures. This prevents padding-oracle attacks. raise DecryptionFailed end self end
encrypt!(public_key_or_secret)
click to toggle source
# File lib/json/jwe.rb, line 29 def encrypt!(public_key_or_secret) self.public_key_or_secret = with_jwk_support public_key_or_secret cipher.encrypt self.content_encryption_key = generate_content_encryption_key self.mac_key, self.encryption_key = derive_encryption_and_mac_keys cipher.key = encryption_key self.iv = cipher.random_iv # NOTE: 'iv' has to be set after 'key' for GCM self.auth_data = Base64.urlsafe_encode64 header.to_json, padding: false cipher.auth_data = auth_data if gcm? self.cipher_text = cipher.update(plain_text) + cipher.final self end
to_s()
click to toggle source
# File lib/json/jwe.rb, line 72 def to_s [ header.to_json, jwe_encrypted_key, iv, cipher_text, authentication_tag ].collect do |segment| Base64.urlsafe_encode64 segment.to_s, padding: false end.join('.') end
Private Instance Methods
authentication_tag()
click to toggle source
# File lib/json/jwe.rb, line 203 def authentication_tag @authentication_tag ||= case when gcm? cipher.auth_tag when cbc? secured_input = [ auth_data, iv, cipher_text, BinData::Uint64be.new(auth_data.length * 8).to_binary_s ].join OpenSSL::HMAC.digest( sha_digest, mac_key, secured_input )[0, sha_size / 2 / 8] end end
cbc?()
click to toggle source
# File lib/json/jwe.rb, line 115 def cbc? [:'A128CBC-HS256', :'A256CBC-HS512'].include? encryption_method&.to_sym end
cipher()
click to toggle source
# File lib/json/jwe.rb, line 123 def cipher raise "#{cipher_name} isn't supported" unless OpenSSL::Cipher.ciphers.include?(cipher_name) @cipher ||= OpenSSL::Cipher.new cipher_name end
cipher_name()
click to toggle source
# File lib/json/jwe.rb, line 128 def cipher_name case encryption_method&.to_sym when :A128GCM 'aes-128-gcm' when :A256GCM 'aes-256-gcm' when :'A128CBC-HS256' 'aes-128-cbc' when :'A256CBC-HS512' 'aes-256-cbc' else raise UnexpectedAlgorithm.new('Unknown Encryption Algorithm') end end
decrypt_content_encryption_key()
click to toggle source
decryption
# File lib/json/jwe.rb, line 222 def decrypt_content_encryption_key fake_content_encryption_key = generate_content_encryption_key # NOTE: do this always not to make timing difference case alg&.to_sym when :RSA1_5 private_key_or_secret.private_decrypt jwe_encrypted_key when :'RSA-OAEP' private_key_or_secret.private_decrypt jwe_encrypted_key, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING when :A128KW, :A256KW AESKeyWrap.unwrap jwe_encrypted_key, private_key_or_secret when :dir private_key_or_secret when :'ECDH-ES' raise NotImplementedError.new('ECDH-ES not supported yet') when :'ECDH-ES+A128KW' raise NotImplementedError.new('ECDH-ES+A128KW not supported yet') when :'ECDH-ES+A256KW' raise NotImplementedError.new('ECDH-ES+A256KW not supported yet') else raise UnexpectedAlgorithm.new('Unknown Encryption Algorithm') end rescue OpenSSL::PKey::PKeyError fake_content_encryption_key end
derive_encryption_and_mac_keys()
click to toggle source
# File lib/json/jwe.rb, line 158 def derive_encryption_and_mac_keys case when gcm? [:wont_be_used, content_encryption_key] when cbc? content_encryption_key.unpack( "a#{content_encryption_key.length / 2}" * 2 ) end end
dir?()
click to toggle source
# File lib/json/jwe.rb, line 119 def dir? :dir == alg&.to_sym end
gcm?()
click to toggle source
common
# File lib/json/jwe.rb, line 111 def gcm? [:A128GCM, :A256GCM].include? encryption_method&.to_sym end
generate_content_encryption_key()
click to toggle source
# File lib/json/jwe.rb, line 192 def generate_content_encryption_key case when dir? public_key_or_secret when gcm? cipher.random_key when cbc? SecureRandom.random_bytes sha_size / 8 end end
jwe_encrypted_key()
click to toggle source
encryption
# File lib/json/jwe.rb, line 171 def jwe_encrypted_key @jwe_encrypted_key ||= case alg&.to_sym when :RSA1_5 public_key_or_secret.public_encrypt content_encryption_key when :'RSA-OAEP' public_key_or_secret.public_encrypt content_encryption_key, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING when :A128KW, :A256KW AESKeyWrap.wrap content_encryption_key, public_key_or_secret when :dir '' when :'ECDH-ES' raise NotImplementedError.new('ECDH-ES not supported yet') when :'ECDH-ES+A128KW' raise NotImplementedError.new('ECDH-ES+A128KW not supported yet') when :'ECDH-ES+A256KW' raise NotImplementedError.new('ECDH-ES+A256KW not supported yet') else raise UnexpectedAlgorithm.new('Unknown Encryption Algorithm') end end
sha_digest()
click to toggle source
# File lib/json/jwe.rb, line 154 def sha_digest OpenSSL::Digest.new "SHA#{sha_size}" end
sha_size()
click to toggle source
# File lib/json/jwe.rb, line 143 def sha_size case encryption_method&.to_sym when :'A128CBC-HS256' 256 when :'A256CBC-HS512' 512 else raise UnexpectedAlgorithm.new('Unknown Hash Size') end end
verify_cbc_authentication_tag!()
click to toggle source
# File lib/json/jwe.rb, line 246 def verify_cbc_authentication_tag! secured_input = [ auth_data, iv, cipher_text, BinData::Uint64be.new(auth_data.length * 8).to_binary_s ].join expected_authentication_tag = OpenSSL::HMAC.digest( sha_digest, mac_key, secured_input )[0, sha_size / 2 / 8] unless secure_compare(authentication_tag, expected_authentication_tag) raise DecryptionFailed end end