Commit 2546e4d
Changed files (4)
spec/xml/kit/certificate_spec.rb
@@ -6,95 +6,84 @@ RSpec.describe Xml::Kit::Certificate do
let(:certificate) { generate_key_pair('password')[0] }
describe '#fingerprint' do
- it 'returns a fingerprint' do
- expect(subject.fingerprint).to be_instance_of(Xml::Kit::Fingerprint)
- end
+ specify { expect(subject.fingerprint).to be_instance_of(Xml::Kit::Fingerprint) }
end
describe '#for?' do
- it 'returns true, when it is for signing' do
- subject = described_class.new(certificate, use: :signing)
- expect(subject).to be_for(:signing)
- expect(subject).to be_signing
- expect(subject).not_to be_for(:encryption)
- expect(subject).not_to be_encryption
+ context 'when it is for signing' do
+ subject { described_class.new(certificate, use: :signing) }
+
+ specify { expect(subject).to be_for(:signing) }
+ specify { expect(subject).to be_signing }
+ specify { expect(subject).not_to be_for(:encryption) }
+ specify { expect(subject).not_to be_encryption }
end
- it 'returns true, when it is for encryption' do
- subject = described_class.new(certificate, use: :encryption)
- expect(subject).to be_for(:encryption)
- expect(subject).not_to be_for(:signing)
+ context 'when it is for encryption' do
+ subject { described_class.new(certificate, use: :encryption) }
- expect(subject).to be_encryption
- expect(subject).not_to be_signing
+ specify { expect(subject).to be_for(:encryption) }
+ specify { expect(subject).not_to be_for(:signing) }
+ specify { expect(subject).to be_encryption }
+ specify { expect(subject).not_to be_signing }
end
- it 'returns true when it is for both' do
- subject = described_class.new(certificate)
- expect(subject).to be_for(:encryption)
- expect(subject).to be_for(:signing)
+ context 'when it is for both signing and encryption' do
+ subject { described_class.new(certificate) }
- expect(subject).to be_encryption
- expect(subject).to be_signing
+ specify { expect(subject).to be_for(:encryption) }
+ specify { expect(subject).to be_for(:signing) }
+ specify { expect(subject).to be_encryption }
+ specify { expect(subject).to be_signing }
end
end
describe 'equality' do
- it 'is equal by reference equality' do
- expect(subject).to eql(subject)
- end
-
- it 'is equal by value equality' do
- expect(
- described_class.new(certificate, use: :signing)
- ).to eql(
- described_class.new(certificate, use: :signing)
- )
- end
+ specify { expect(subject).to eql(subject) }
+ specify { expect(described_class.new(certificate, use: :signing)).to eql(described_class.new(certificate, use: :signing)) }
end
describe '#to_h' do
- it 'returns a hash' do
- expect(subject.to_h).to eql(
- use: :signing,
- fingerprint: subject.fingerprint.to_s
- )
- end
+ specify { expect(subject.to_h).to eql(use: :signing, fingerprint: subject.fingerprint.to_s) }
end
describe '#stripped' do
- it 'removes the BEGIN and END lines' do
- expected = certificate.to_s.gsub(/-----BEGIN CERTIFICATE-----/, '').gsub(/-----END CERTIFICATE-----/, '').delete("\n")
- expect(subject.stripped).to eql(expected)
- end
+ let(:expected) { certificate.to_s.gsub(/-----BEGIN CERTIFICATE-----/, '').gsub(/-----END CERTIFICATE-----/, '').delete("\n") }
+
+ specify { expect(subject.stripped).to eql(expected) }
end
describe '#x509' do
- it 'returns an x509 certificate' do
- expected = OpenSSL::X509::Certificate.new(certificate.to_s)
- actual = subject.x509
- expect(actual).to be_instance_of(OpenSSL::X509::Certificate)
- expect(actual.to_s).to eql(expected.to_s)
- end
+ let(:expected) { OpenSSL::X509::Certificate.new(certificate.to_s) }
+ let(:actual) { subject.x509 }
+
+ specify { expect(actual).to be_instance_of(OpenSSL::X509::Certificate) }
+ specify { expect(actual.to_s).to eql(expected.to_s) }
end
describe '#expired?' do
let(:certificate) { OpenSSL::X509::Certificate.new }
- it 'returns false, when the certificate has not expired yet' do
- certificate.not_before = 1.minute.ago
- certificate.not_after = 10.minutes.from_now
+ context 'when the certificate has not expired yet' do
+ subject { described_class.new(certificate, use: :signing) }
- subject = described_class.new(certificate, use: :signing)
- expect(subject).not_to be_expired(Time.now)
+ before do
+ certificate.not_before = 1.minute.ago
+ certificate.not_after = 10.minutes.from_now
+ end
+
+ specify { expect(subject).not_to be_expired(Time.now) }
end
- it 'returns true, when the current time is after the time of expiration' do
- certificate.not_before = 10.minutes.ago
- certificate.not_after = 1.minute.ago
+ context 'when the current time is after the time of the expiration' do
+ subject { described_class.new(certificate, use: :signing) }
+
+ before do
+ certificate.not_before = 10.minutes.ago
+ certificate.not_after = 1.minute.ago
+ end
- subject = described_class.new(certificate, use: :signing)
- expect(subject).to be_expired(Time.now)
+ specify { expect(subject).to be_expired(Time.now) }
end
end
@@ -166,29 +155,27 @@ RSpec.describe Xml::Kit::Certificate do
certificate.not_after = 10.minutes.from_now
end
- it 'delegates not_after to the x509 certificate' do
- expect(subject.not_after).to eql(certificate.not_after)
- end
-
- it 'delegates not_before to the x509 certificate' do
- expect(subject.not_before).to eql(certificate.not_before)
- end
+ specify { expect(subject.not_after).to eql(certificate.not_after) }
+ specify { expect(subject.not_before).to eql(certificate.not_before) }
end
describe '#to_xml' do
- it 'generates the correct xml' do
- result = Hash.from_xml(subject.to_xml)
- expect(result['KeyDescriptor']).to be_present
- expect(result['KeyDescriptor']['use']).to eql('signing')
- expect(result['KeyDescriptor']['KeyInfo']['xmlns']).to eql(Xml::Kit::Namespaces::XMLDSIG)
- expect(result['KeyDescriptor']['KeyInfo']['X509Data']['X509Certificate']).to eql(subject.stripped)
+ context 'when generated' do
+ let(:result) { Hash.from_xml(subject.to_xml) }
+
+ specify { expect(result['KeyDescriptor']).to be_present }
+ specify { expect(result['KeyDescriptor']['use']).to eql('signing') }
+ specify { expect(result['KeyDescriptor']['KeyInfo']['xmlns']).to eql(Xml::Kit::Namespaces::XMLDSIG) }
+ specify { expect(result['KeyDescriptor']['KeyInfo']['X509Data']['X509Certificate']).to eql(subject.stripped) }
end
- it 'omits the `use` when the cert can be used for both signing and encryption' do
- subject = described_class.new(certificate, use: nil)
- result = Hash.from_xml(subject.to_xml)
- expect(result['KeyDescriptor']).to be_present
- expect(result['KeyDescriptor']['use']).to be_nil
+ context 'when the certificate can be used for both signing and encryption' do
+ subject { described_class.new(certificate, use: nil) }
+
+ let(:result) { Hash.from_xml(subject.to_xml) }
+
+ specify { expect(result['KeyDescriptor']).to be_present }
+ specify { expect(result['KeyDescriptor']['use']).to be_nil }
end
end
end
spec/xml/kit/decryption_spec.rb
@@ -5,161 +5,163 @@ RSpec.describe Xml::Kit::Decryption do
let(:secret) { FFaker::Movie.title }
let(:password) { FFaker::Movie.title }
- it 'decrypts AES-128-CBC data' do
- certificate_pem, private_key_pem = generate_key_pair(password)
-
- public_key = OpenSSL::X509::Certificate.new(certificate_pem).public_key
- private_key = OpenSSL::PKey::RSA.new(private_key_pem, password)
-
- cipher = OpenSSL::Cipher.new('AES-128-CBC')
- cipher.encrypt
- key = cipher.random_key
- iv = cipher.random_iv
- encrypted = cipher.update(secret) + cipher.final
-
- data = {
- 'EncryptedData' => {
- 'xmlns:xenc' => 'http://www.w3.org/2001/04/xmlenc#',
- 'xmlns:dsig' => 'http://www.w3.org/2000/09/xmldsig#',
- 'Type' => 'http://www.w3.org/2001/04/xmlenc#Element',
- 'EncryptionMethod' => {
- 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
- },
- 'KeyInfo' => {
+ context 'when decrypting AES-128-CBC data' do
+ subject { described_class.new(private_keys: [private_key]) }
+
+ let(:key_pair) { generate_key_pair(password) }
+ let(:certificate_pem) { key_pair[0] }
+ let(:private_key_pem) { key_pair[1] }
+ let(:public_key) { OpenSSL::X509::Certificate.new(certificate_pem).public_key }
+ let(:private_key) { OpenSSL::PKey::RSA.new(private_key_pem, password) }
+ let(:data) do
+ cipher = OpenSSL::Cipher.new('AES-128-CBC')
+ cipher.encrypt
+ key = cipher.random_key
+ iv = cipher.random_iv
+ encrypted = cipher.update(secret) + cipher.final
+ {
+ 'EncryptedData' => {
+ 'xmlns:xenc' => 'http://www.w3.org/2001/04/xmlenc#',
'xmlns:dsig' => 'http://www.w3.org/2000/09/xmldsig#',
- 'EncryptedKey' => {
- 'EncryptionMethod' => {
- 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
- },
- 'CipherData' => {
- 'CipherValue' => Base64.encode64(public_key.public_encrypt(key))
+ 'Type' => 'http://www.w3.org/2001/04/xmlenc#Element',
+ 'EncryptionMethod' => {
+ 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
+ },
+ 'KeyInfo' => {
+ 'xmlns:dsig' => 'http://www.w3.org/2000/09/xmldsig#',
+ 'EncryptedKey' => {
+ 'EncryptionMethod' => {
+ 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
+ },
+ 'CipherData' => {
+ 'CipherValue' => Base64.encode64(public_key.public_encrypt(key))
+ }
}
+ },
+ 'CipherData' => {
+ 'CipherValue' => Base64.encode64(iv + encrypted)
}
- },
- 'CipherData' => {
- 'CipherValue' => Base64.encode64(iv + encrypted)
}
}
- }
- subject = described_class.new(private_keys: [private_key])
- decrypted = subject.decrypt_hash(data)
- expect(decrypted.strip).to eql(secret)
+ end
+
+ specify { expect(subject.decrypt_hash(data).strip).to eql(secret) }
end
- it 'attempts to decrypt with each encryption keypair' do
- certificate_pem, private_key_pem = generate_key_pair(password)
- public_key = OpenSSL::X509::Certificate.new(certificate_pem).public_key
- private_key = OpenSSL::PKey::RSA.new(private_key_pem, password)
-
- cipher = OpenSSL::Cipher.new('AES-128-CBC')
- cipher.encrypt
- key = cipher.random_key
- iv = cipher.random_iv
- encrypted = cipher.update(secret) + cipher.final
-
- data = {
- 'EncryptedData' => {
- 'xmlns:xenc' => 'http://www.w3.org/2001/04/xmlenc#',
- 'xmlns:dsig' => 'http://www.w3.org/2000/09/xmldsig#',
- 'Type' => 'http://www.w3.org/2001/04/xmlenc#Element',
- 'EncryptionMethod' => {
- 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
- },
- 'KeyInfo' => {
+ context 'when multiple encryption keys are present' do
+ subject { described_class.new(private_keys: [other_private_key, private_key]) }
+
+ let(:key_pair) { generate_key_pair(password) }
+ let(:other_key_pair) { generate_key_pair(password) }
+ let(:certificate_pem) { key_pair[0] }
+ let(:private_key_pem) { key_pair[1] }
+ let(:public_key) { OpenSSL::X509::Certificate.new(certificate_pem).public_key }
+ let(:private_key) { OpenSSL::PKey::RSA.new(private_key_pem, password) }
+ let(:other_private_key_pem) { other_key_pair[1] }
+ let(:other_private_key) { OpenSSL::PKey::RSA.new(other_private_key_pem, password) }
+
+ let(:data) do
+ cipher = OpenSSL::Cipher.new('AES-128-CBC')
+ cipher.encrypt
+ key = cipher.random_key
+ iv = cipher.random_iv
+ encrypted = cipher.update(secret) + cipher.final
+
+ {
+ 'EncryptedData' => {
+ 'xmlns:xenc' => 'http://www.w3.org/2001/04/xmlenc#',
'xmlns:dsig' => 'http://www.w3.org/2000/09/xmldsig#',
- 'EncryptedKey' => {
- 'EncryptionMethod' => {
- 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
- },
- 'CipherData' => {
- 'CipherValue' => Base64.encode64(public_key.public_encrypt(key))
+ 'Type' => 'http://www.w3.org/2001/04/xmlenc#Element',
+ 'EncryptionMethod' => {
+ 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
+ },
+ 'KeyInfo' => {
+ 'xmlns:dsig' => 'http://www.w3.org/2000/09/xmldsig#',
+ 'EncryptedKey' => {
+ 'EncryptionMethod' => {
+ 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
+ },
+ 'CipherData' => {
+ 'CipherValue' => Base64.encode64(public_key.public_encrypt(key))
+ }
}
+ },
+ 'CipherData' => {
+ 'CipherValue' => Base64.encode64(iv + encrypted)
}
- },
- 'CipherData' => {
- 'CipherValue' => Base64.encode64(iv + encrypted)
}
}
- }
-
- _, other_private_key_pem = generate_key_pair(password)
- other_private_key = OpenSSL::PKey::RSA.new(other_private_key_pem, password)
+ end
- subject = described_class.new(private_keys: [other_private_key, private_key])
- decrypted = subject.decrypt_hash(data)
- expect(decrypted.strip).to eql(secret)
+ specify { expect(subject.decrypt_hash(data).strip).to eql(secret) }
end
- it 'raise an error when it cannot decrypt the data' do
- certificate_pem, = generate_key_pair(password)
- public_key = OpenSSL::X509::Certificate.new(certificate_pem).public_key
-
- cipher = OpenSSL::Cipher.new('AES-128-CBC')
- cipher.encrypt
- key = cipher.random_key
- iv = cipher.random_iv
- encrypted = cipher.update(secret) + cipher.final
-
- data = {
- 'EncryptedData' => {
- 'xmlns:xenc' => 'http://www.w3.org/2001/04/xmlenc#',
- 'xmlns:dsig' => 'http://www.w3.org/2000/09/xmldsig#',
- 'Type' => 'http://www.w3.org/2001/04/xmlenc#Element',
- 'EncryptionMethod' => {
- 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
- },
- 'KeyInfo' => {
+ context 'when it cannot decrypt the data' do
+ subject { described_class.new(private_keys: [new_private_key]) }
+
+ let(:key_pair) { generate_key_pair(password) }
+ let(:certificate_pem) { key_pair[0] }
+ let(:public_key) { OpenSSL::X509::Certificate.new(certificate_pem).public_key }
+ let(:new_private_key_pem) { generate_key_pair(password)[1] }
+ let(:new_private_key) { OpenSSL::PKey::RSA.new(new_private_key_pem, password) }
+ let(:data) do
+ cipher = OpenSSL::Cipher.new('AES-128-CBC')
+ cipher.encrypt
+ key = cipher.random_key
+ iv = cipher.random_iv
+ encrypted = cipher.update(secret) + cipher.final
+
+ {
+ 'EncryptedData' => {
+ 'xmlns:xenc' => 'http://www.w3.org/2001/04/xmlenc#',
'xmlns:dsig' => 'http://www.w3.org/2000/09/xmldsig#',
- 'EncryptedKey' => {
- 'EncryptionMethod' => {
- 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
- },
- 'CipherData' => {
- 'CipherValue' => Base64.encode64(public_key.public_encrypt(key))
+ 'Type' => 'http://www.w3.org/2001/04/xmlenc#Element',
+ 'EncryptionMethod' => {
+ 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
+ },
+ 'KeyInfo' => {
+ 'xmlns:dsig' => 'http://www.w3.org/2000/09/xmldsig#',
+ 'EncryptedKey' => {
+ 'EncryptionMethod' => {
+ 'Algorithm' => 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
+ },
+ 'CipherData' => {
+ 'CipherValue' => Base64.encode64(public_key.public_encrypt(key))
+ }
}
+ },
+ 'CipherData' => {
+ 'CipherValue' => Base64.encode64(iv + encrypted)
}
- },
- 'CipherData' => {
- 'CipherValue' => Base64.encode64(iv + encrypted)
}
}
- }
-
- new_private_key_pem = generate_key_pair(password)[1]
- new_private_key = OpenSSL::PKey::RSA.new(new_private_key_pem, password)
- subject = described_class.new(private_keys: [new_private_key])
- expect do
- subject.decrypt_hash(data)
- end.to raise_error(OpenSSL::PKey::RSAError)
+ end
+
+ specify { expect { subject.decrypt_hash(data) }.to raise_error(OpenSSL::PKey::RSAError) }
end
end
describe '#decrypt_document' do
+ subject { described_class.new(private_keys: [item.encryption_key_pair.private_key]) }
+
let(:item) { Item.new }
let(:document) { Nokogiri::XML(item.to_xml) }
- let(:subject) { described_class.new(private_keys: [item.encryption_key_pair.private_key]) }
let(:encrypted_node) { document.at_xpath('/Item/Encrypted/xmlenc:EncryptedData', 'xmlenc' => 'http://www.w3.org/2001/04/xmlenc#') }
- it 'decrypts a nokogiri document' do
- expect(subject.decrypt_node(encrypted_node).name).to eql('EncryptMe')
- end
+ specify { expect(subject.decrypt_node(encrypted_node).name).to eql('EncryptMe') }
+ specify { expect(subject.decrypt_node(nil)).to be_nil }
- it 'returns the node when it does not contain an EncryptedData' do
- document = Nokogiri::XML('<hello><world></world></hello>')
- node = document.at_xpath('//hello/world')
- expect(subject.decrypt_node(node)).to eql(node)
- end
+ context 'when it does not contain an EncryptedData' do
+ let(:document) { Nokogiri::XML('<hello><world></world></hello>') }
+ let(:node) { document.at_xpath('//hello/world') }
- it 'returns nil when the node is nil' do
- expect(subject.decrypt_node(nil)).to be_nil
+ specify { expect(subject.decrypt_node(node)).to eql(node) }
end
- it 'raises an error when the document cannot be decrypted' do
- subject = described_class.new(private_keys: [])
+ context 'when the document cannot be decrypted' do
+ subject { described_class.new(private_keys: []) }
- expect do
- subject.decrypt_node(encrypted_node)
- end.to raise_error(Xml::Kit::DecryptionError)
+ specify { expect { subject.decrypt_node(encrypted_node) }.to raise_error(Xml::Kit::DecryptionError) }
end
end
end
spec/xml/kit/document_spec.rb
@@ -2,34 +2,43 @@
RSpec.describe Xml::Kit::Document do
describe '#valid_signature?' do
- let(:login_url) { "https://#{FFaker::Internet.domain_name}/login" }
- let(:logout_url) { "https://#{FFaker::Internet.domain_name}/logout" }
let(:signed_xml) { Item.new.to_xml }
- it 'returns true, when the digest and signature is valid' do
- expect(described_class.new(signed_xml)).to be_valid
+ context 'when the signature is valid' do
+ subject { described_class.new(signed_xml) }
+
+ specify { expect(subject).to be_valid }
end
- it 'returns false, when the SHA1 digest is not valid' do
- subject = described_class.new(signed_xml.gsub('Item', 'uhoh'))
- expect(subject).not_to be_valid
- expect(subject.errors[:digest_value]).to be_present
+ context 'when the SHA1 digest is not valid' do
+ subject { described_class.new(signed_xml.gsub('Item', 'uhoh')) }
+
+ before { subject.valid? }
+
+ specify { expect(subject).not_to be_valid }
+ specify { expect(subject.errors[:digest_value]).to be_present }
end
- it 'is invalid when digest is incorrect' do
- old_digest = Hash.from_xml(signed_xml)['Item']['Signature']['SignedInfo']['Reference']['DigestValue']
+ context 'when the digest is incorrect' do
+ subject { described_class.new(signed_xml.gsub(old_digest, 'sabotage')) }
+
+ let(:old_digest) { Hash.from_xml(signed_xml)['Item']['Signature']['SignedInfo']['Reference']['DigestValue'] }
- subject = described_class.new(signed_xml.gsub(old_digest, 'sabotage'))
- expect(subject).not_to be_valid
- expect(subject.errors[:digest_value]).to be_present
+ before { subject.valid? }
+
+ specify { expect(subject).not_to be_valid }
+ specify { expect(subject.errors[:digest_value]).to be_present }
end
- it 'returns false, when the signature is invalid' do
- old_signature = Hash.from_xml(signed_xml)['Item']['Signature']['SignatureValue']
- signed_xml.gsub!(old_signature, 'sabotage')
- subject = described_class.new(signed_xml)
- expect(subject).not_to be_valid
- expect(subject.errors[:signature]).to be_present
+ context 'when the signature is invalid' do
+ subject { described_class.new(signed_xml.gsub(old_signature, 'sabotage')) }
+
+ let(:old_signature) { Hash.from_xml(signed_xml)['Item']['Signature']['SignatureValue'] }
+
+ before { subject.valid? }
+
+ specify { expect(subject).not_to be_valid }
+ specify { expect(subject.errors[:signature]).to be_present }
end
context 'when the certificate is expired' do
@@ -48,7 +57,7 @@ RSpec.describe Xml::Kit::Document do
expired_certificate.sign(private_key, digest_algorithm)
end
- it 'is invalid' do
+ specify do
certificate = ::Xml::Kit::Certificate.new(expired_certificate)
item.sign_with(certificate.to_key_pair(private_key))
subject = described_class.new(item.to_xml)
spec/xml/kit/fingerprint_spec.rb
@@ -2,19 +2,17 @@
RSpec.describe Xml::Kit::Fingerprint do
describe '#sha' do
+ let(:key_pair) { generate_key_pair('password') }
+ let(:certificate) { key_pair[0] }
+ let(:x509) { OpenSSL::X509::Certificate.new(certificate) }
+
it 'returns the SHA256' do
- certificate, = generate_key_pair('password')
- x509 = OpenSSL::X509::Certificate.new(certificate)
sha256 = OpenSSL::Digest::SHA256.new.hexdigest(x509.to_der).upcase.scan(/../).join(':')
-
expect(described_class.new(certificate).algorithm(OpenSSL::Digest::SHA256)).to eql(sha256)
end
it 'returns the SHA1' do
- certificate, = generate_key_pair('password')
- x509 = OpenSSL::X509::Certificate.new(certificate)
sha1 = OpenSSL::Digest::SHA1.new.hexdigest(x509.to_der).upcase.scan(/../).join(':')
-
expect(described_class.new(certificate).algorithm(OpenSSL::Digest::SHA1)).to eql(sha1)
end
end