certificate-transparency-client 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/certificate-transparency-client.gemspec +2 -0
- data/lib/certificate-transparency-client.rb +0 -1
- data/lib/certificate-transparency/client.rb +1 -0
- metadata +16 -10
- data/lib/certificate-transparency.rb +0 -14
- data/lib/certificate-transparency/constants.rb +0 -23
- data/lib/certificate-transparency/extensions/string.rb +0 -15
- data/lib/certificate-transparency/extensions/time.rb +0 -17
- data/lib/certificate-transparency/signed_tree_head.rb +0 -51
- data/lib/tls.rb +0 -24
- data/lib/tls/digitally_signed.rb +0 -129
- data/lib/tls/opaque.rb +0 -106
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d688032d7f32de9399ffac2339ae54897650bfd5
|
4
|
+
data.tar.gz: a2127485b2aa9657dc4534ed4c147de6ca25d565
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 353cbf08a26d3d9e9a9adaaf355fa1c33bf7192fafb45fb1e6cc2696fb3453d5094a1fa01e61f58071e4874c513e260ef02cf4bb9e3c63fc1e912922ebb9d678
|
7
|
+
data.tar.gz: b12f6515e8aa005bd3199c814544da926f3a4f2b669e11d9784f19ace14d02ed065366ffba81186c5077ecccd8a0bd44ff84c61a7d9caaa80557341b0f7b96dc
|
@@ -22,6 +22,8 @@ Gem::Specification.new do |s|
|
|
22
22
|
|
23
23
|
s.required_ruby_version = ">= 1.9.3"
|
24
24
|
|
25
|
+
s.add_runtime_dependency 'certificate-transparency', '~> 0.0'
|
26
|
+
|
25
27
|
s.add_development_dependency 'bundler'
|
26
28
|
s.add_development_dependency 'github-release'
|
27
29
|
s.add_development_dependency 'guard-spork'
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: certificate-transparency-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Palmer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-05-
|
11
|
+
date: 2015-05-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: certificate-transparency
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -156,15 +170,7 @@ files:
|
|
156
170
|
- certificate-transparency-client.gemspec
|
157
171
|
- lib/.gitkeep
|
158
172
|
- lib/certificate-transparency-client.rb
|
159
|
-
- lib/certificate-transparency.rb
|
160
173
|
- lib/certificate-transparency/client.rb
|
161
|
-
- lib/certificate-transparency/constants.rb
|
162
|
-
- lib/certificate-transparency/extensions/string.rb
|
163
|
-
- lib/certificate-transparency/extensions/time.rb
|
164
|
-
- lib/certificate-transparency/signed_tree_head.rb
|
165
|
-
- lib/tls.rb
|
166
|
-
- lib/tls/digitally_signed.rb
|
167
|
-
- lib/tls/opaque.rb
|
168
174
|
homepage: http://theshed.hezmatt.org/certificate-transparency-client
|
169
175
|
licenses: []
|
170
176
|
metadata: {}
|
@@ -1,14 +0,0 @@
|
|
1
|
-
# The base module of everything related to Certificate Transparency.
|
2
|
-
module CertificateTransparency; end
|
3
|
-
|
4
|
-
unless Kernel.const_defined?(:CT)
|
5
|
-
#:nodoc:
|
6
|
-
CT = CertificateTransparency
|
7
|
-
end
|
8
|
-
|
9
|
-
require 'certificate-transparency/extensions/string'
|
10
|
-
require 'certificate-transparency/extensions/time'
|
11
|
-
|
12
|
-
require 'certificate-transparency/constants'
|
13
|
-
|
14
|
-
require 'certificate-transparency/signed_tree_head'
|
@@ -1,23 +0,0 @@
|
|
1
|
-
module CertificateTransparency
|
2
|
-
# RFC6962 s3.1
|
3
|
-
LogEntryType = {
|
4
|
-
:x509_entry => 0,
|
5
|
-
:precert_entry => 1
|
6
|
-
}
|
7
|
-
|
8
|
-
# RFC6962 s3.4
|
9
|
-
MerkleLeafType = {
|
10
|
-
:timestamped_entry => 0
|
11
|
-
}
|
12
|
-
|
13
|
-
# RFC6962 s3.2
|
14
|
-
SignatureType = {
|
15
|
-
:certificate_timestamp => 0,
|
16
|
-
:tree_hash => 1
|
17
|
-
}
|
18
|
-
|
19
|
-
# RFC6962 s3.2
|
20
|
-
Version = {
|
21
|
-
:v1 => 0
|
22
|
-
}
|
23
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
# Extensions to the String class.
|
2
|
-
#
|
3
|
-
class String
|
4
|
-
# Return a new string, which is simply the object base64 encoded.
|
5
|
-
#
|
6
|
-
def base64
|
7
|
-
[self.to_s].pack("m0")
|
8
|
-
end
|
9
|
-
|
10
|
-
# Return a new string, which is simply the object base64 decoded.
|
11
|
-
#
|
12
|
-
def unbase64
|
13
|
-
self.to_s.unpack("m").first
|
14
|
-
end
|
15
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
# Extensions to the Time class.
|
2
|
-
#
|
3
|
-
class Time
|
4
|
-
# Return the time represented by this object, in milliseconds since the
|
5
|
-
# epoch.
|
6
|
-
#
|
7
|
-
def ms
|
8
|
-
(self.to_f * 1000).to_i
|
9
|
-
end
|
10
|
-
|
11
|
-
# Create a new instance of Time, set to the given number of milliseconds
|
12
|
-
# since the epoch.
|
13
|
-
#
|
14
|
-
def self.ms(i)
|
15
|
-
Time.at(i.to_f / 1000)
|
16
|
-
end
|
17
|
-
end
|
@@ -1,51 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
require 'tls'
|
3
|
-
|
4
|
-
# A CT SignedTreeHead (RFC6962 s3.5, s4.3).
|
5
|
-
#
|
6
|
-
class CertificateTransparency::SignedTreeHead
|
7
|
-
attr_accessor :tree_size
|
8
|
-
attr_accessor :timestamp
|
9
|
-
attr_accessor :root_hash
|
10
|
-
attr_accessor :signature
|
11
|
-
|
12
|
-
# Create a new SignedTreeHead instance from the JSON returned
|
13
|
-
# by `/ct/v1/get-sth`.
|
14
|
-
#
|
15
|
-
def self.from_json(json)
|
16
|
-
doc = JSON.parse(json)
|
17
|
-
|
18
|
-
self.new.tap do |sth|
|
19
|
-
sth.tree_size = doc['tree_size']
|
20
|
-
sth.timestamp = Time.at(doc['timestamp'].to_f / 1000)
|
21
|
-
sth.root_hash = doc['sha256_root_hash'].unpack("m").first
|
22
|
-
sth.signature = doc['tree_head_signature'].unpack("m").first
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# Determine whether or not the signature that was provided in the
|
27
|
-
# signed tree head is a valid one, based on the provided key.
|
28
|
-
#
|
29
|
-
# @param pk [String] the raw binary form of the public key of the
|
30
|
-
# log.
|
31
|
-
#
|
32
|
-
# @return Boolean
|
33
|
-
#
|
34
|
-
def valid?(pk)
|
35
|
-
key = OpenSSL::PKey::EC.new(pk)
|
36
|
-
|
37
|
-
blob = [
|
38
|
-
CT::Version[:v1],
|
39
|
-
CT::SignatureType[:tree_hash],
|
40
|
-
timestamp.ms,
|
41
|
-
tree_size,
|
42
|
-
root_hash
|
43
|
-
].pack("ccQ>Q>a32")
|
44
|
-
|
45
|
-
ds = TLS::DigitallySigned.from_blob(signature)
|
46
|
-
ds.content = blob
|
47
|
-
ds.key = key
|
48
|
-
|
49
|
-
ds.valid?
|
50
|
-
end
|
51
|
-
end
|
data/lib/tls.rb
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
# Constants and types required by CertificateTransparency, which come from
|
2
|
-
# the core TLS specs.
|
3
|
-
#
|
4
|
-
module TLS
|
5
|
-
# RFC5246 s7.4.1.4.1 (I shit you not, five levels of headings)
|
6
|
-
HashAlgorithm = { :none => 0,
|
7
|
-
:md5 => 1,
|
8
|
-
:sha1 => 2,
|
9
|
-
:sha224 => 3,
|
10
|
-
:sha256 => 4,
|
11
|
-
:sha384 => 5,
|
12
|
-
:sha512 => 6
|
13
|
-
}
|
14
|
-
|
15
|
-
# RFC5246 s7.4.1.4.1
|
16
|
-
SignatureAlgorithm = { :anonymous => 0,
|
17
|
-
:rsa => 1,
|
18
|
-
:dsa => 2,
|
19
|
-
:ecdsa => 3
|
20
|
-
}
|
21
|
-
end
|
22
|
-
|
23
|
-
require 'tls/digitally_signed'
|
24
|
-
require 'tls/opaque'
|
data/lib/tls/digitally_signed.rb
DELETED
@@ -1,129 +0,0 @@
|
|
1
|
-
require 'openssl'
|
2
|
-
|
3
|
-
unless OpenSSL::PKey::EC.instance_methods.include?(:private?)
|
4
|
-
OpenSSL::PKey::EC.class_eval("alias_method :private?, :private_key?")
|
5
|
-
end
|
6
|
-
|
7
|
-
# Create a `DigitallySigned` struct, as defined by RFC5246 s4.7, and adapted
|
8
|
-
# for the CertificateTransparency system (that is, ECDSA using the NIST
|
9
|
-
# P-256 curve is the only signature algorithm supported, and SHA-256 is the
|
10
|
-
# only hash algorithm supported).
|
11
|
-
#
|
12
|
-
class TLS::DigitallySigned
|
13
|
-
# Create a new `DigitallySigned` struct.
|
14
|
-
#
|
15
|
-
# Takes a number of named options:
|
16
|
-
#
|
17
|
-
# * `:key` -- (required) An instance of `OpenSSL::PKey::EC`. If you pass
|
18
|
-
# in `:blob` as well, then this can be either a public key or a private
|
19
|
-
# key (because you only need a public key for validating a signature),
|
20
|
-
# but if you only pass in `:content`, you must provide a private key
|
21
|
-
# here.
|
22
|
-
#
|
23
|
-
# This key *must* be generated with the NIST P-256 curve (known to
|
24
|
-
# OpenSSL as `prime256v1`) in order to be compliant with the CT spec.
|
25
|
-
# However, we can't validate that, so it's up to you to make sure you
|
26
|
-
# do it right.
|
27
|
-
#
|
28
|
-
# * `:content` -- (required) The content to sign, or verify the signature
|
29
|
-
# of. This can be any string.
|
30
|
-
#
|
31
|
-
# * `:blob` -- An existing encoded `DigitallySigned` struct you'd like to
|
32
|
-
# have decoded and verified against `:content` with `:key`.
|
33
|
-
#
|
34
|
-
# Raises an `ArgumentError` if you try to pass in anything that doesn't
|
35
|
-
# meet the rather stringent requirements.
|
36
|
-
#
|
37
|
-
def self.from_blob(blob)
|
38
|
-
hash_algorithm, signature_algorithm, sig_blob = blob.unpack("CCa*")
|
39
|
-
|
40
|
-
if signature_algorithm != ::TLS::SignatureAlgorithm[:ecdsa]
|
41
|
-
raise ArgumentError,
|
42
|
-
"Signature specified in blob is not ECDSA"
|
43
|
-
end
|
44
|
-
|
45
|
-
if hash_algorithm != ::TLS::HashAlgorithm[:sha256]
|
46
|
-
raise ArgumentError,
|
47
|
-
"Hash algorithm specified in blob is not SHA256"
|
48
|
-
end
|
49
|
-
|
50
|
-
sig, rest = ::TLS::Opaque.from_blob(sig_blob, 2**16-1)
|
51
|
-
signature = sig.value
|
52
|
-
|
53
|
-
TLS::DigitallySigned.new.tap do |ds|
|
54
|
-
ds.hash_algorithm = hash_algorithm
|
55
|
-
ds.signature_algorithm = signature_algorithm
|
56
|
-
ds.signature = signature
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
attr_accessor :content, :hash_algorithm, :signature_algorithm, :signature
|
61
|
-
attr_reader :key
|
62
|
-
|
63
|
-
# Set the key for this instance.
|
64
|
-
#
|
65
|
-
# @param k [OpenSSL::PKey::EC] a key to verify or generate the signature.
|
66
|
-
# If you only want to verify an existing signature (ie you created this
|
67
|
-
# instance via {.from_blob}, then this key can be a public key.
|
68
|
-
# Otherwise, if you want to generate a new signature, you must pass in
|
69
|
-
# a private key.
|
70
|
-
#
|
71
|
-
# @return void
|
72
|
-
#
|
73
|
-
# @raise [ArgumentError] if you pass in a key that isn't of the
|
74
|
-
# appropriate type.
|
75
|
-
#
|
76
|
-
def key=(k)
|
77
|
-
unless k.is_a? OpenSSL::PKey::EC
|
78
|
-
raise ArgumentError,
|
79
|
-
"Key must be an instance of OpenSSL::PKey::EC"
|
80
|
-
end
|
81
|
-
|
82
|
-
@key = k
|
83
|
-
end
|
84
|
-
|
85
|
-
# Return a binary string which represents a `DigitallySigned` struct of
|
86
|
-
# the content passed in.
|
87
|
-
#
|
88
|
-
def to_blob
|
89
|
-
if @key.nil?
|
90
|
-
raise RuntimeError,
|
91
|
-
"No key has been supplied"
|
92
|
-
end
|
93
|
-
begin
|
94
|
-
@signature ||= @key.sign(OpenSSL::Digest::SHA256.new, @content)
|
95
|
-
rescue ArgumentError
|
96
|
-
raise RuntimeError,
|
97
|
-
"Must have a private key in order to make a signature"
|
98
|
-
end
|
99
|
-
|
100
|
-
[
|
101
|
-
@hash_algorithm,
|
102
|
-
@signature_algorithm,
|
103
|
-
@signature.length,
|
104
|
-
@signature
|
105
|
-
].pack("CCna*").force_encoding("BINARY")
|
106
|
-
end
|
107
|
-
|
108
|
-
# Verify whether or not the `signature` struct given is a valid signature
|
109
|
-
# for the key/content/blob combination provided to the constructor.
|
110
|
-
#
|
111
|
-
def valid?
|
112
|
-
if @key.nil?
|
113
|
-
raise RuntimeError,
|
114
|
-
"No key has been specified"
|
115
|
-
end
|
116
|
-
|
117
|
-
if @signature.nil?
|
118
|
-
raise RuntimeError,
|
119
|
-
"No signature is available yet"
|
120
|
-
end
|
121
|
-
|
122
|
-
if @content.nil?
|
123
|
-
raise RuntimeError,
|
124
|
-
"No content has been specified yet"
|
125
|
-
end
|
126
|
-
|
127
|
-
@key.verify(OpenSSL::Digest::SHA256.new, @signature, @content)
|
128
|
-
end
|
129
|
-
end
|
data/lib/tls/opaque.rb
DELETED
@@ -1,106 +0,0 @@
|
|
1
|
-
# An implementation of the TLS 1.2 (RFC5246) "variable length" opaque type.
|
2
|
-
#
|
3
|
-
# You can create an instance of this type by passing in a stringish to be
|
4
|
-
# encoded, and a "maximum length", like this:
|
5
|
-
#
|
6
|
-
# TLS::Opaque.new("Hello World", 2**16-1)
|
7
|
-
#
|
8
|
-
# If you have a TLS::Opaque-encoded blob, and you'd like to get the
|
9
|
-
# content out of it, you can use `.from_blob` to create a TLS::Opaque object
|
10
|
-
# that will contain the data you seek:
|
11
|
-
#
|
12
|
-
# TLS::Opaque.from_blob("\x00\x0BHello World", 2**16-1)
|
13
|
-
#
|
14
|
-
# In both cases, you need to specify what the maximum length of the `value`
|
15
|
-
# can be, because that is what determines how many bytes the length field
|
16
|
-
# takes up at the beginning of the string.
|
17
|
-
#
|
18
|
-
# To get the "encoded" form,, call `#to_blob`:
|
19
|
-
#
|
20
|
-
# TLS::Opaque.new("Hello World", 255).to_blob
|
21
|
-
# => "\x0BHello World"
|
22
|
-
#
|
23
|
-
# Or, to get the string itself out, call `#value`:
|
24
|
-
#
|
25
|
-
# TLS::Opaque.from_blob("\x0BHello World", 255)[0].value
|
26
|
-
# => "Hello World"
|
27
|
-
#
|
28
|
-
# Passing in a value or blob which is longer than the maximum length
|
29
|
-
# specified will result in `ArgumentError` being thrown.
|
30
|
-
#
|
31
|
-
class TLS::Opaque
|
32
|
-
attr_reader :value
|
33
|
-
|
34
|
-
# Parse out an opaque string from a blob, as well as returning
|
35
|
-
# any remaining data. The `maxlen` parameter is required to
|
36
|
-
# know how many octets at the beginning of the string to read to
|
37
|
-
# determine the length of the opaque string.
|
38
|
-
#
|
39
|
-
# Returns a two-element array, `[TLS::Opaque, String]`, being a
|
40
|
-
# `TLS::Opaque` instance retrieved from the blob provided, and a `String`
|
41
|
-
# containing any remainder of the blob that wasn't considered part of the
|
42
|
-
# `TLS::Opaque`. This second element will *always* be a string, but it
|
43
|
-
# may be an empty string, if the `TLS::Opaque` instance was the entire
|
44
|
-
# blob.
|
45
|
-
#
|
46
|
-
# This method will raise `ArgumentError` if the length encoded at the
|
47
|
-
# beginning of `blob` is longer than the data in `blob`, or if it is
|
48
|
-
# larger than `maxlen`.
|
49
|
-
#
|
50
|
-
def self.from_blob(blob, maxlen)
|
51
|
-
len_bytes = lenlen(maxlen)
|
52
|
-
|
53
|
-
len = blob[0..len_bytes-1].split('').inject(0) do |total, c|
|
54
|
-
total * 256 + c.ord
|
55
|
-
end
|
56
|
-
|
57
|
-
if len > maxlen
|
58
|
-
raise ArgumentError,
|
59
|
-
"Encoded length (#{len}) is greater than maxlen (#{maxlen})"
|
60
|
-
end
|
61
|
-
|
62
|
-
if len > blob[len_bytes..-1].length
|
63
|
-
raise ArgumentError,
|
64
|
-
"Encoded length (#{len}) is greater than the number of bytes available"
|
65
|
-
end
|
66
|
-
|
67
|
-
[TLS::Opaque.new(blob[len_bytes..(len_bytes+len-1)], maxlen),
|
68
|
-
blob[(len_bytes+len)..-1]
|
69
|
-
]
|
70
|
-
end
|
71
|
-
|
72
|
-
def initialize(str, maxlen)
|
73
|
-
unless maxlen.is_a? Integer
|
74
|
-
raise ArgumentError,
|
75
|
-
"maxlen must be an Integer"
|
76
|
-
end
|
77
|
-
|
78
|
-
if str.length > maxlen
|
79
|
-
raise ArgumentError,
|
80
|
-
"value given is longer than maxlen (#{maxlen})"
|
81
|
-
end
|
82
|
-
|
83
|
-
@maxlen = maxlen
|
84
|
-
@value = str
|
85
|
-
end
|
86
|
-
|
87
|
-
# Return an encoded Opaque.
|
88
|
-
#
|
89
|
-
def to_blob
|
90
|
-
len = value.length
|
91
|
-
params = []
|
92
|
-
self.class.lenlen(@maxlen).times do
|
93
|
-
params.unshift(len % 256)
|
94
|
-
len /= 256
|
95
|
-
end
|
96
|
-
|
97
|
-
params << value
|
98
|
-
|
99
|
-
params.pack("C#{self.class.lenlen(@maxlen)}a*")
|
100
|
-
end
|
101
|
-
|
102
|
-
private
|
103
|
-
def self.lenlen(len)
|
104
|
-
(Math.log2(len).ceil / 8.0).ceil
|
105
|
-
end
|
106
|
-
end
|