certificate-transparency-client 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|