certificate-transparency 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e4ea6b3e2c15000d7eaca39ef6b3661c0bab1865
4
- data.tar.gz: 093bb71663c96c018b54f6c8604ae69a3f439e38
3
+ metadata.gz: 39b2b99f63286dade01640df20a331e7c1281018
4
+ data.tar.gz: 98f432392e75038c15eaa17090e239501dc8011e
5
5
  SHA512:
6
- metadata.gz: 0d4c7c9b2788e6103c6a2382f2810ff3e2e4ae0b62251f1f53d9c739aa115897de996b53f269f92510e03b1a431df8f0c27c4c096f7fdba0a46c66e7a7b42805
7
- data.tar.gz: 105393ac84c748cf4cef092c2550653c00fc3d1ae99de0543c0ef77b7e4796bb230ea603f4429781d4f58a24226542364db4f345edba54e1cc232e6f289fab60
6
+ metadata.gz: 4b1d533fafad47199195eaf133fe8cdbcc7889897fd413336ed8d48bbbaf466bcc566f2238cd16c459df2a0f34020d356556b6b623ccbf9a05dded51cd67ab2f
7
+ data.tar.gz: d49e9c47f74b63f6c8b5bf1c063d34f730abd84dd7282beb8bb06ac28162f988bd01b46acb026f5d0aad2977964cd30954f79a99bb4b1b8b14ff75b58772b91e
@@ -0,0 +1,126 @@
1
+ # An RFC6962 MerkleTreeLeaf structure
2
+ #
3
+ # Use {.from_blob} if you have an encoded MTL you wish to decode, or
4
+ # else create a new instance, pass in a `TimestampedEntry` object via
5
+ # `#timestamped_entry=`, and then call `#to_blob` to get the encoded MTL.
6
+ #
7
+ class CertificateTransparency::MerkleTreeLeaf
8
+ attr_reader :timestamped_entry
9
+
10
+ # Return a new MerkleTreeLeaf instance, from a binary blob of data.
11
+ # Raises an ArgumentError if the blob is invalid in some way.
12
+ #
13
+ # @param blob [String]
14
+ #
15
+ # @return [CertificateTransparency::MerkleTreeLeaf]
16
+ #
17
+ def self.from_blob(blob)
18
+ new.tap do |mtl|
19
+ mtl.version, leaf_type, te = blob.unpack("CCa*")
20
+ unless leaf_type == ::CertificateTransparency::MerkleLeafType[:timestamped_entry]
21
+ raise ArgumentError,
22
+ "Unknown leaf type in blob"
23
+ end
24
+
25
+ mtl.timestamped_entry =
26
+ ::CertificateTransparency::TimestampedEntry.from_blob(te)
27
+ end
28
+ end
29
+
30
+ # Instantiate a new MerkleTreeLeaf.
31
+ #
32
+ def initialize
33
+ @version = ::CertificateTransparency::Version[:v1]
34
+ @leaf_type = ::CertificateTransparency::MerkleLeafType[:timestamped_entry]
35
+ end
36
+
37
+ # Set the version of the MerkleTreeLeaf structure to create. At present,
38
+ # only `:v1` is supported, so there isn't much point in ever calling this
39
+ # method.
40
+ #
41
+ # @param v [Symbol]
42
+ #
43
+ # @return void
44
+ #
45
+ def version=(v)
46
+ @version = case v
47
+ when Symbol
48
+ ::CertificateTransparency::Version[v]
49
+ when Integer
50
+ v
51
+ else
52
+ nil
53
+ end
54
+
55
+ if @version.nil? or !::CertificateTransparency::Version.values.include?(@version)
56
+ raise ArgumentError,
57
+ "Invalid version #{v.inspect}"
58
+ end
59
+ end
60
+
61
+ # Return a symbol indicating the version of the MerkleTreeLeaf structure
62
+ # represented by this object. At present, only `:v1` is supported.
63
+ #
64
+ # @return Symbol
65
+ #
66
+ def version
67
+ ::CertificateTransparency::Version.invert[@version]
68
+ end
69
+
70
+ # Set the leaf type of the MerkleTreeLeaf structure. At present, only
71
+ # `:timestamped_entry` is supported, so there isn't much point in ever
72
+ # calling this method.
73
+ #
74
+ # @param lt [Symbol]
75
+ #
76
+ # @return void
77
+ #
78
+ def leaf_type=(lt)
79
+ @leaf_type = ::CertificateTransparency::MerkleLeafType[lt]
80
+
81
+ if @leaf_type.nil?
82
+ raise ArgumentError,
83
+ "Invalid leaf_type #{lt.inspect}"
84
+ end
85
+ end
86
+
87
+ # Return a symbol indicating the leaf type of the MerkleTreeLeaf
88
+ # structure represented by this object. At present, only
89
+ # `:timestamped_entry` is supported.
90
+ #
91
+ # @return Symbol
92
+ #
93
+ def leaf_type
94
+ ::CertificateTransparency::MerkleLeafType.invert[@leaf_type]
95
+ end
96
+
97
+ # Set the TimestampedEntry element for this MerkleTreeLeaf. It must be
98
+ # an instance of CertificateTransparency::TimestampedEntry, or an
99
+ # ArgumentError will be raised.
100
+ #
101
+ # @param te [CertificateTransparency::TimestampedEntry]
102
+ #
103
+ # @return void
104
+ #
105
+ def timestamped_entry=(te)
106
+ unless te.is_a? ::CertificateTransparency::TimestampedEntry
107
+ raise ArgumentError,
108
+ "Wasn't passed a TimestampedEntry (got a #{te.class})"
109
+ end
110
+
111
+ @timestamped_entry = te
112
+ end
113
+
114
+ # Generate a binary blob representing this MerkleTreeLeaf structure.
115
+ #
116
+ # @return [String]
117
+ #
118
+ def to_blob
119
+ if @timestamped_entry.nil?
120
+ raise RuntimeError,
121
+ "timestamped_entry has not been set"
122
+ end
123
+
124
+ [@version, @leaf_type, @timestamped_entry.to_blob].pack("CCa*")
125
+ end
126
+ end
@@ -0,0 +1,166 @@
1
+ # An RFC6962 TimestampedEntry structure
2
+ #
3
+ # Use {.from_blob} if you have an encoded TE you wish to decode, or create a
4
+ # new instance, set the various parameters, and use `#to_blob` to give you
5
+ # an encoded structure you can put over the wire. The various elements of
6
+ # the TE struct are available via accessors.
7
+ #
8
+ class CertificateTransparency::TimestampedEntry
9
+ # An instance of Time representing the timestamp of this entry
10
+ #
11
+ # @return [Time]
12
+ #
13
+ attr_reader :timestamp
14
+
15
+ # The type of entry we've got here. Is a symbol, either
16
+ # :x509_entry or :precert_entry.
17
+ #
18
+ # @return [Symbol]
19
+ #
20
+ attr_reader :entry_type
21
+
22
+ # An OpenSSL::X509::Certificate instance, if `entry_type == :x509_entry`,
23
+ # or nil otherwise.
24
+ #
25
+ # @return [OpenSSL::X509::Certificate]
26
+ #
27
+ attr_reader :x509_entry
28
+
29
+ # An instance of ::CertificateTransparency::PreCert if `entry_type ==
30
+ # :precert_entry`, or nil otherwise.
31
+ #
32
+ # @return [CertificateTransparency::PreCert]
33
+ #
34
+ attr_reader :precert_entry
35
+
36
+ # Create a new {CT::TimestampedEntry} by decoding a binary blob.
37
+ #
38
+ # @param blob [String]
39
+ #
40
+ # @return [CertificateTransparency::TimestampedEntry]
41
+ #
42
+ # @raise [ArgumentError] if we can't understand how to decode the
43
+ # provided blob.
44
+ #
45
+ def self.from_blob(blob)
46
+ ts, entry_type, rest = blob.unpack("Q>na*")
47
+
48
+ new.tap do |te|
49
+ te.timestamp = Time.ms(ts)
50
+
51
+ case CertificateTransparency::LogEntryType.invert[entry_type]
52
+ when :x509_entry
53
+ cert_data, rest = TLS::Opaque.from_blob(rest, 2**24-1)
54
+ te.x509_entry = OpenSSL::X509::Certificate.new(cert_data.value)
55
+ when :precert_entry
56
+ # Holy fuck, can I have ASN1 back, please? I can't just pass
57
+ # the PreCert part of the blob into CT::PreCert.new, because I
58
+ # can't parse the PreCert part out of the blob without digging
59
+ # *into* the PreCert part, because the only information on how
60
+ # long TBSCertificate is is contained *IN THE PRECERT!*
61
+ #
62
+ # I'm surprised there aren't a lot more bugs in TLS
63
+ # implementations, if this is how they lay out their data
64
+ # structures.
65
+ ikh, tbsc_len_hi, tbsc_len_lo, rest = rest.unpack("a32nCa*")
66
+ tbsc_len = tbsc_len_hi * 256 + tbsc_len_lo
67
+ tbsc, rest = rest.unpack("a#{tbsc_len}a*")
68
+ te.precert_entry = ::CertificateTransparency::PreCert.new do |ctpc|
69
+ ctpc.issuer_key_hash = ikh
70
+ ctpc.tbs_certificate = tbsc
71
+ end
72
+ else
73
+ raise ArgumentError,
74
+ "Unknown LogEntryType: #{entry_type} (corrupt TimestampedEntry?)"
75
+ end
76
+
77
+ exts, rest = TLS::Opaque.from_blob(rest, 2**16-1)
78
+ unless exts.value == ""
79
+ raise ArgumentError,
80
+ "Non-empty extensions found (#{exts.value.inspect})"
81
+ end
82
+
83
+ unless rest == ""
84
+ raise ArgumentError,
85
+ "Corrupted blob (garbage data after extensions)"
86
+ end
87
+ end
88
+ end
89
+
90
+ # Gives you whichever of `#x509_entry` or `#precert_entry` is
91
+ # not nil, or `nil` if both of them are `nil`.
92
+ #
93
+ # @return [OpenSSL::X509::Certificate, CertificateTransparency::PreCert]
94
+ #
95
+ def signed_entry
96
+ @x509_entry or @precert_entry
97
+ end
98
+
99
+ # Set the timestamp for this entry
100
+ #
101
+ # Must be a Time object, or something that can be bludgeoned
102
+ # into a Time object.
103
+ #
104
+ # @param ts [Time]
105
+ #
106
+ # @return void
107
+ #
108
+ def timestamp=(ts)
109
+ unless ts.is_a? Time or ts.respond_to? :to_time
110
+ raise ArgumentError,
111
+ "Must pass me a Time or something that responds to :to_time"
112
+ end
113
+
114
+ @timestamp = ts.is_a?(Time) ? ts : ts.to_time
115
+ end
116
+
117
+ # Set the entry to be an x509_entry with the given certificate.
118
+ #
119
+ # @param xe [OpenSSL::X509::Certificate]
120
+ #
121
+ # @return void
122
+ #
123
+ def x509_entry=(xe)
124
+ @x509_entry = OpenSSL::X509::Certificate.new(xe.to_s)
125
+ @entry_type = :x509_entry
126
+ @precert_entry = nil
127
+ end
128
+
129
+ # Set the entry to be a precert_entry with the given precert data. You
130
+ # must pass in a CertificateTransparency::PreCert instance.
131
+ #
132
+ # @param pe [CertificateTransparency::PreCert]
133
+ #
134
+ # @return void
135
+ #
136
+ def precert_entry=(pe)
137
+ unless pe.is_a? ::CertificateTransparency::PreCert
138
+ raise ArgumentError,
139
+ "I only accept PreCert instances (you gave me a #{pe.class})"
140
+ end
141
+
142
+ @precert_entry = pe
143
+ @entry_type = :precert_entry
144
+ @x509_entry = nil
145
+ end
146
+
147
+ # Encode this {TimestampedEntry} into a binary blob.
148
+ #
149
+ # @return [String]
150
+ #
151
+ def to_blob
152
+ signed_entry = if @x509_entry
153
+ TLS::Opaque.new(@x509_entry.to_der, 2**24-1).to_blob
154
+ elsif @precert_entry
155
+ @precert_entry.to_blob
156
+ else
157
+ raise RuntimeError,
158
+ "You must call #precert_entry= or #x509_entry= before calling #to_blob"
159
+ end
160
+
161
+ [@timestamp.ms,
162
+ CertificateTransparency::LogEntryType[entry_type],
163
+ signed_entry, 0
164
+ ].pack("Q>na*n")
165
+ end
166
+ end
@@ -28,7 +28,9 @@ unless Kernel.const_defined?(:CT)
28
28
  CT = CertificateTransparency
29
29
  end
30
30
 
31
- require 'certificate-transparency/extensions/string'
32
- require 'certificate-transparency/extensions/time'
31
+ require_relative 'certificate-transparency/extensions/string'
32
+ require_relative 'certificate-transparency/extensions/time'
33
33
 
34
- require 'certificate-transparency/signed_tree_head'
34
+ require_relative 'certificate-transparency/merkle_tree_leaf'
35
+ require_relative 'certificate-transparency/signed_tree_head'
36
+ require_relative 'certificate-transparency/timestamped_entry'
@@ -62,9 +62,9 @@ class TLS::DigitallySigned
62
62
 
63
63
  # Set the key for this instance.
64
64
  #
65
- # @param k [OpenSSL::PKey::EC] a key to verify or generate the signature.
65
+ # @param k [OpenSSL::PKey::EC] a key to verify or generate the signature.
66
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.
67
+ # instance via {.from_blob}, then this key can be a public key.
68
68
  # Otherwise, if you want to generate a new signature, you must pass in
69
69
  # a private key.
70
70
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: certificate-transparency
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Palmer
@@ -159,7 +159,9 @@ files:
159
159
  - lib/certificate-transparency.rb
160
160
  - lib/certificate-transparency/extensions/string.rb
161
161
  - lib/certificate-transparency/extensions/time.rb
162
+ - lib/certificate-transparency/merkle_tree_leaf.rb
162
163
  - lib/certificate-transparency/signed_tree_head.rb
164
+ - lib/certificate-transparency/timestamped_entry.rb
163
165
  - lib/tls.rb
164
166
  - lib/tls/digitally_signed.rb
165
167
  - lib/tls/opaque.rb