Class: Fabric::ECCryptoSuite

Inherits:
Object
  • Object
show all
Defined in:
lib/fabric/ec_crypto_suite.rb

Overview

TODO:

missing tests

Elliptic-curve Crypto Suite using OpenSSL

Constant Summary collapse

DEFAULT_KEY_SIZE =

rubocop:disable Metrics/ClassLength

256
DEFAULT_DIGEST_ALGORITHM =
'SHA256'
DEFAULT_AES_KEY_SIZE =
128
EC_CURVES =
{ 256 => 'prime256v1', 384 => 'secp384r1' }.freeze
CIPHER =
'aes-256-cbc'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ ECCryptoSuite

Returns a new instance of ECCryptoSuite.



23
24
25
26
27
28
29
# File 'lib/fabric/ec_crypto_suite.rb', line 23

def initialize(opts = {})
  @key_size = opts[:key_size] || DEFAULT_KEY_SIZE
  @digest_algorithm = opts[:digest_algorithm] || DEFAULT_DIGEST_ALGORITHM
  @digest_instance = OpenSSL::Digest.new digest_algorithm
  @curve = EC_CURVES[key_size]
  @cipher = opts[:cipher] || CIPHER
end

Instance Attribute Details

#cipherObject (readonly)

Returns the value of attribute cipher.



21
22
23
# File 'lib/fabric/ec_crypto_suite.rb', line 21

def cipher
  @cipher
end

#curveObject (readonly)

Returns the value of attribute curve.



21
22
23
# File 'lib/fabric/ec_crypto_suite.rb', line 21

def curve
  @curve
end

#digest_algorithmObject (readonly)

Returns the value of attribute digest_algorithm.



21
22
23
# File 'lib/fabric/ec_crypto_suite.rb', line 21

def digest_algorithm
  @digest_algorithm
end

#digest_instanceObject (readonly)

Returns the value of attribute digest_instance.



21
22
23
# File 'lib/fabric/ec_crypto_suite.rb', line 21

def digest_instance
  @digest_instance
end

#key_sizeObject (readonly)

Returns the value of attribute key_size.



21
22
23
# File 'lib/fabric/ec_crypto_suite.rb', line 21

def key_size
  @key_size
end

Instance Method Details

#address_from_public_key(public_key) ⇒ Object



96
97
98
99
100
101
# File 'lib/fabric/ec_crypto_suite.rb', line 96

def address_from_public_key(public_key)
  bytes = decode_hex public_key
  address_bytes = digest(bytes[1..])[-20..]

  encode_hex address_bytes
end

#build_shared_key(private_key, public_key) ⇒ Object



103
104
105
106
107
108
109
110
# File 'lib/fabric/ec_crypto_suite.rb', line 103

def build_shared_key(private_key, public_key)
  pkey = pkey_from_private_key private_key
  public_bn = OpenSSL::BN.new public_key, 16
  group = OpenSSL::PKey::EC::Group.new curve
  public_point = OpenSSL::PKey::EC::Point.new group, public_bn

  encode_hex pkey.dh_compute_key(public_point)
end

#decode_hex(string) ⇒ Object



83
84
85
# File 'lib/fabric/ec_crypto_suite.rb', line 83

def decode_hex(string)
  [string].pack('H*')
end

#decrypt(secret, data) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/fabric/ec_crypto_suite.rb', line 122

def decrypt(secret, data)
  return unless data

  encrypted_data = Base64.strict_decode64 data
  aes = OpenSSL::Cipher.new cipher
  aes.decrypt
  aes.key = decode_hex(secret)
  aes.iv = encrypted_data[0..15]
  encrypted_data = encrypted_data[16..]

  aes.update(encrypted_data) + aes.final
end

#digest(message) ⇒ Object



75
76
77
# File 'lib/fabric/ec_crypto_suite.rb', line 75

def digest(message)
  @digest_instance.digest message
end

#encode_hex(bytes) ⇒ Object



79
80
81
# File 'lib/fabric/ec_crypto_suite.rb', line 79

def encode_hex(bytes)
  bytes.unpack1('H*')
end

#encrypt(secret, data) ⇒ Object



112
113
114
115
116
117
118
119
120
# File 'lib/fabric/ec_crypto_suite.rb', line 112

def encrypt(secret, data)
  aes = OpenSSL::Cipher.new cipher
  aes.encrypt
  aes.key = decode_hex(secret)
  iv = aes.random_iv
  aes.iv = iv

  Base64.strict_encode64(iv + aes.update(data) + aes.final)
end

#generate_csr(private_key, attrs = []) ⇒ Object



56
57
58
59
60
61
62
63
64
65
# File 'lib/fabric/ec_crypto_suite.rb', line 56

def generate_csr(private_key, attrs = [])
  key = pkey_from_private_key private_key

  req = OpenSSL::X509::Request.new
  req.public_key = key
  req.subject = OpenSSL::X509::Name.new attrs
  req.sign key, @digest_instance

  req
end

#generate_nonce(length = 24) ⇒ Object



67
68
69
# File 'lib/fabric/ec_crypto_suite.rb', line 67

def generate_nonce(length = 24)
  OpenSSL::Random.random_bytes length
end

#generate_private_keyObject



50
51
52
53
54
# File 'lib/fabric/ec_crypto_suite.rb', line 50

def generate_private_key
  key = OpenSSL::PKey::EC.generate(curve)

  key.private_key.to_s(16).downcase
end

#hexdigest(message) ⇒ Object



71
72
73
# File 'lib/fabric/ec_crypto_suite.rb', line 71

def hexdigest(message)
  @digest_instance.hexdigest message
end

#key_from_pem(pem) ⇒ Object



168
169
170
171
# File 'lib/fabric/ec_crypto_suite.rb', line 168

def key_from_pem(pem)
  key = OpenSSL::PKey::EC.new(pem)
  key.private_key.to_s(16).downcase
end

#pem_from_private_key(private_key) ⇒ Object

rubocop:enable Metrics/AbcSize rubocop:enable Metrics/MethodLength



162
163
164
165
166
# File 'lib/fabric/ec_crypto_suite.rb', line 162

def pem_from_private_key(private_key)
  pkey = pkey_from_private_key(private_key)

  pkey.to_pem
end

#pkey_from_private_key(private_key) ⇒ Object

when https://github.com/ruby/openssl/pull/555 gets merged, consider refactoring the code here rubocop:disable Metrics/AbcSize rubocop:disable Metrics/MethodLength



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/fabric/ec_crypto_suite.rb', line 139

def pkey_from_private_key(private_key)
  public_key = restore_public_key private_key

  group = OpenSSL::PKey::EC::Group.new(curve)

  private_key_bn   = OpenSSL::BN.new(private_key, 16)
  public_key_bn    = OpenSSL::BN.new(public_key, 16)
  public_key_point = OpenSSL::PKey::EC::Point.new(group, public_key_bn)

  asn1 = OpenSSL::ASN1::Sequence(
    [
      OpenSSL::ASN1::Integer.new(1),
      OpenSSL::ASN1::OctetString(private_key_bn.to_s(2)),
      OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT),
      OpenSSL::ASN1::BitString(public_key_point.to_octet_string(:uncompressed), 1, :EXPLICIT)
    ]
  )

  OpenSSL::PKey::EC.new(asn1.to_der)
end

#pkey_from_public_key(public_key) ⇒ Object

rubocop:disable Metrics/MethodLength



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/fabric/ec_crypto_suite.rb', line 179

def pkey_from_public_key(public_key)
  group = OpenSSL::PKey::EC::Group.new(curve)

  public_key_bn    = OpenSSL::BN.new(public_key, 16)
  public_key_point = OpenSSL::PKey::EC::Point.new(group, public_key_bn)

  asn1 = OpenSSL::ASN1::Sequence.new(
    [
      OpenSSL::ASN1::Sequence.new([
                                    OpenSSL::ASN1::ObjectId.new('id-ecPublicKey'),
                                    OpenSSL::ASN1::ObjectId.new(group.curve_name)
                                  ]),
      OpenSSL::ASN1::BitString.new(public_key_point.to_octet_string(:uncompressed))
    ]
  )

  OpenSSL::PKey::EC.new(asn1.to_der)
end

#public_key_from_x509_certificate(certificate) ⇒ Object



173
174
175
176
# File 'lib/fabric/ec_crypto_suite.rb', line 173

def public_key_from_x509_certificate(certificate)
  cert = OpenSSL::X509::Certificate.new(certificate)
  cert.public_key.public_key.to_bn.to_s(16).downcase
end

#restore_public_key(private_key) ⇒ Object



87
88
89
90
91
92
93
94
# File 'lib/fabric/ec_crypto_suite.rb', line 87

def restore_public_key(private_key)
  private_bn = OpenSSL::BN.new private_key, 16
  group = OpenSSL::PKey::EC::Group.new curve
  public_bn = group.generator.mul(private_bn).to_bn
  public_bn = OpenSSL::PKey::EC::Point.new(group, public_bn).to_bn

  public_bn.to_s(16).downcase
end

#sign(private_key, message) ⇒ Object



31
32
33
34
35
36
37
38
39
# File 'lib/fabric/ec_crypto_suite.rb', line 31

def sign(private_key, message)
  digest = digest message
  key = pkey_from_private_key private_key
  signature = key.dsa_sign_asn1 digest
  sequence = OpenSSL::ASN1.decode signature
  sequence = prevent_malleability sequence, key.group.order

  sequence.to_der
end

#verify(public_key, message, signature) ⇒ Object



41
42
43
44
45
46
47
48
# File 'lib/fabric/ec_crypto_suite.rb', line 41

def verify(public_key, message, signature)
  digest = digest message
  openssl_pkey = pkey_from_public_key public_key
  sequence = OpenSSL::ASN1.decode signature
  return false unless check_malleability sequence, openssl_pkey.group.order

  openssl_pkey.dsa_verify_asn1(digest, signature)
end