Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(59)

Side by Side Diff: net/base/transport_security_state_static_generate.go

Issue 10003002: Revert 132012 - net: move HSTS preloaded and pinning info out of code. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 8 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « net/base/transport_security_state_static.json ('k') | net/net.gyp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // This program converts the information in
6 // transport_security_state_static.json and
7 // transport_security_state_static.certs into
8 // transport_security_state_static.h. The input files contain information about
9 // public key pinning and HTTPS-only sites that is compiled into Chromium.
10
11 // Run as:
12 // % go run transport_security_state_static_generate.go transport_security_state _static.json transport_security_state_static.certs
13 //
14 // It will write transport_security_state_static.h
15
16 package main
17
18 import (
19 "bufio"
20 "bytes"
21 "crypto/sha1"
22 "crypto/x509"
23 "encoding/base64"
24 "encoding/json"
25 "encoding/pem"
26 "errors"
27 "fmt"
28 "io"
29 "os"
30 "regexp"
31 "strings"
32 )
33
34 // A pin represents an entry in transport_security_state_static.certs. It's a
35 // name associated with a SubjectPublicKeyInfo hash and, optionally, a
36 // certificate.
37 type pin struct {
38 name string
39 cert *x509.Certificate
40 spkiHash []byte
41 spkiHashFunc string // i.e. "sha1"
42 }
43
44 // preloaded represents the information contained in the
45 // transport_security_state_static.json file. This structure and the two
46 // following are used by the "json" package to parse the file. See the comments
47 // in transport_security_state_static.json for details.
48 type preloaded struct {
49 Pinsets []pinset `json:"pinsets"`
50 Entries []hsts `json:"entries"`
51 }
52
53 type pinset struct {
54 Name string `json:"name"`
55 Include []string `json:"static_spki_hashes"`
56 Exclude []string `json:"bad_static_spki_hashes"`
57 }
58
59 type hsts struct {
60 Name string `json:"name"`
61 Subdomains bool `json:"include_subdomains"`
62 Mode string `json:"mode"`
63 Pins string `json:"pins"`
64 SNIOnly bool `json:"snionly"`
65 }
66
67 func main() {
68 if len(os.Args) != 3 {
69 fmt.Fprintf(os.Stderr, "Usage: %s <json file> <certificates file >\n", os.Args[0])
70 os.Exit(1)
71 }
72
73 if err := process(os.Args[1], os.Args[2]); err != nil {
74 fmt.Fprintf(os.Stderr, "Conversion failed: %s\n", err.Error())
75 os.Exit(1)
76 }
77 }
78
79 func process(jsonFileName, certsFileName string) error {
80 jsonFile, err := os.Open(jsonFileName)
81 if err != nil {
82 return fmt.Errorf("failed to open input file: %s\n", err.Error() )
83 }
84 defer jsonFile.Close()
85
86 jsonBytes, err := removeComments(jsonFile)
87 if err != nil {
88 return fmt.Errorf("failed to remove comments from JSON: %s\n", e rr.Error())
89 }
90
91 var preloaded preloaded
92 if err := json.Unmarshal(jsonBytes, &preloaded); err != nil {
93 return fmt.Errorf("failed to parse JSON: %s\n", err.Error())
94 }
95
96 certsFile, err := os.Open(certsFileName)
97 if err != nil {
98 return fmt.Errorf("failed to open input file: %s\n", err.Error() )
99 }
100 defer certsFile.Close()
101
102 pins, err := parseCertsFile(certsFile)
103 if err != nil {
104 return fmt.Errorf("failed to parse certificates file: %s\n", err )
105 }
106
107 if err := checkDuplicatePins(pins); err != nil {
108 return err
109 }
110
111 if err := checkCertsInPinsets(preloaded.Pinsets, pins); err != nil {
112 return err
113 }
114
115 outFile, err := os.OpenFile("transport_security_state_static.h", os.O_WR ONLY|os.O_CREATE|os.O_TRUNC, 0644)
116 if err != nil {
117 return err
118 }
119 defer outFile.Close()
120
121 out := bufio.NewWriter(outFile)
122 writeHeader(out)
123 writeCertsOutput(out, pins)
124 writeHSTSOutput(out, preloaded)
125 writeFooter(out)
126 out.Flush()
127
128 return nil
129 }
130
131 var newLine = []byte("\n")
132 var startOfCert = []byte("-----BEGIN CERTIFICATE")
133 var endOfCert = []byte("-----END CERTIFICATE")
134 var startOfSHA1 = []byte("sha1/")
135
136 // nameRegexp matches valid pin names: an uppercase letter followed by zero or
137 // more letters and digits.
138 var nameRegexp = regexp.MustCompile("[A-Z][a-zA-Z0-9_]*")
139
140 // commentRegexp matches lines that optionally start with whitespace
141 // followed by "//".
142 var commentRegexp = regexp.MustCompile("^[ \t]*//")
143
144 // removeComments reads the contents of |r| and removes any lines beginning
145 // with optional whitespace followed by "//"
146 func removeComments(r io.Reader) ([]byte, error) {
147 var buf bytes.Buffer
148 in := bufio.NewReader(r)
149
150 for {
151 line, isPrefix, err := in.ReadLine()
152 if isPrefix {
153 return nil, errors.New("line too long in JSON")
154 }
155 if err == io.EOF {
156 break
157 }
158 if err != nil {
159 return nil, err
160 }
161 if commentRegexp.Match(line) {
162 continue
163 }
164 buf.Write(line)
165 buf.Write(newLine)
166 }
167
168 return buf.Bytes(), nil
169 }
170
171 // parseCertsFile parses |inFile|, in the format of
172 // transport_security_state_static.certs. See the comments at the top of that
173 // file for details of the format.
174 func parseCertsFile(inFile io.Reader) ([]pin, error) {
175 const (
176 PRENAME = iota
177 POSTNAME = iota
178 INCERT = iota
179 )
180
181 in := bufio.NewReader(inFile)
182
183 lineNo := 0
184 var pemCert []byte
185 state := PRENAME
186 var name string
187 var pins []pin
188
189 for {
190 lineNo++
191 line, isPrefix, err := in.ReadLine()
192 if isPrefix {
193 return nil, fmt.Errorf("line %d is too long to process\n ", lineNo)
194 }
195 if err == io.EOF {
196 break
197 }
198 if err != nil {
199 return nil, fmt.Errorf("error reading from input: %s\n", err.Error())
200 }
201
202 if len(line) == 0 || line[0] == '#' {
203 continue
204 }
205
206 switch state {
207 case PRENAME:
208 name = string(line)
209 if !nameRegexp.MatchString(name) {
210 return nil, fmt.Errorf("invalid name on line %d\ n", lineNo)
211 }
212 state = POSTNAME
213 case POSTNAME:
214 switch {
215 case bytes.HasPrefix(line, startOfSHA1):
216 hash, err := base64.StdEncoding.DecodeString(str ing(line[len(startOfSHA1):]))
217 if err != nil {
218 return nil, fmt.Errorf("failed to decode hash on line %d: %s\n", lineNo, err)
219 }
220 if len(hash) != 20 {
221 return nil, fmt.Errorf("bad SHA1 hash le ngth on line %d: %s\n", lineNo, err)
222 }
223 pins = append(pins, pin{
224 name: name,
225 spkiHashFunc: "sha1",
226 spkiHash: hash,
227 })
228 state = PRENAME
229 continue
230 case bytes.HasPrefix(line, startOfCert):
231 pemCert = pemCert[:0]
232 pemCert = append(pemCert, line...)
233 pemCert = append(pemCert, '\n')
234 state = INCERT
235 default:
236 return nil, fmt.Errorf("line %d, after a name, i s not a hash nor a certificate\n", lineNo)
237 }
238 case INCERT:
239 pemCert = append(pemCert, line...)
240 pemCert = append(pemCert, '\n')
241 if !bytes.HasPrefix(line, endOfCert) {
242 continue
243 }
244
245 block, _ := pem.Decode(pemCert)
246 if block == nil {
247 return nil, fmt.Errorf("failed to decode certifi cate ending on line %d\n", lineNo)
248 }
249 cert, err := x509.ParseCertificate(block.Bytes)
250 if err != nil {
251 return nil, fmt.Errorf("failed to parse certific ate ending on line %d: %s\n", lineNo, err.Error())
252 }
253 certName := cert.Subject.CommonName
254 if len(certName) == 0 {
255 certName = cert.Subject.Organization[0] + " " + cert.Subject.OrganizationalUnit[0]
256 }
257 if err := matchNames(certName, name); err != nil {
258 return nil, fmt.Errorf("name failure on line %d: %s\n%s -> %s\n", lineNo, err, certName, name)
259 }
260 h := sha1.New()
261 h.Write(cert.RawSubjectPublicKeyInfo)
262 pins = append(pins, pin{
263 name: name,
264 cert: cert,
265 spkiHashFunc: "sha1",
266 spkiHash: h.Sum(nil),
267 })
268 state = PRENAME
269 }
270 }
271
272 return pins, nil
273 }
274
275 // matchNames returns true if the given pin name is a reasonable match for the
276 // given CN.
277 func matchNames(name, v string) error {
278 words := strings.Split(name, " ")
279 if len(words) == 0 {
280 return errors.New("no words in certificate name")
281 }
282 firstWord := words[0]
283 if strings.HasSuffix(firstWord, ",") {
284 firstWord = firstWord[:len(firstWord)-1]
285 }
286 if pos := strings.Index(firstWord, "."); pos != -1 {
287 firstWord = firstWord[:pos]
288 }
289 if pos := strings.Index(firstWord, "-"); pos != -1 {
290 firstWord = firstWord[:pos]
291 }
292 if !strings.HasPrefix(v, firstWord) {
293 return errors.New("the first word of the certificate name isn't a prefix of the variable name")
294 }
295
296 for i, word := range words {
297 if word == "Class" && i+1 < len(words) {
298 if strings.Index(v, word+words[i+1]) == -1 {
299 return errors.New("class specification doesn't a ppear in the variable name")
300 }
301 } else if len(word) == 1 && word[0] >= '0' && word[0] <= '9' {
302 if strings.Index(v, word) == -1 {
303 return errors.New("number doesn't appear in the variable name")
304 }
305 } else if isImportantWordInCertificateName(word) {
306 if strings.Index(v, word) == -1 {
307 return errors.New(word + " doesn't appear in the variable name")
308 }
309 }
310 }
311
312 return nil
313 }
314
315 // isImportantWordInCertificateName returns true if w must be found in any
316 // corresponding variable name.
317 func isImportantWordInCertificateName(w string) bool {
318 switch w {
319 case "Universal", "Global", "EV", "G1", "G2", "G3", "G4", "G5":
320 return true
321 }
322 return false
323 }
324
325 // checkDuplicatePins returns an error if any pins have the same name or the sam e hash.
326 func checkDuplicatePins(pins []pin) error {
327 seenNames := make(map[string]bool)
328 seenHashes := make(map[string]string)
329
330 for _, pin := range pins {
331 if _, ok := seenNames[pin.name]; ok {
332 return fmt.Errorf("duplicate name: %s", pin.name)
333 }
334 seenNames[pin.name] = true
335
336 strHash := string(pin.spkiHash)
337 if otherName, ok := seenHashes[strHash]; ok {
338 return fmt.Errorf("duplicate hash for %s and %s", pin.na me, otherName)
339 }
340 seenHashes[strHash] = pin.name
341 }
342
343 return nil
344 }
345
346 // checkCertsInPinsets returns an error if
347 // a) unknown pins are mentioned in |pinsets|
348 // b) unused pins are given in |pins|
349 // c) a pinset name is used twice
350 func checkCertsInPinsets(pinsets []pinset, pins []pin) error {
351 pinNames := make(map[string]bool)
352 for _, pin := range pins {
353 pinNames[pin.name] = true
354 }
355
356 usedPinNames := make(map[string]bool)
357 pinsetNames := make(map[string]bool)
358
359 for _, pinset := range pinsets {
360 if _, ok := pinsetNames[pinset.Name]; ok {
361 return fmt.Errorf("duplicate pinset name: %s", pinset.Na me)
362 }
363 pinsetNames[pinset.Name] = true
364
365 var allPinNames []string
366 allPinNames = append(allPinNames, pinset.Include...)
367 allPinNames = append(allPinNames, pinset.Exclude...)
368
369 for _, pinName := range allPinNames {
370 if _, ok := pinNames[pinName]; !ok {
371 return fmt.Errorf("unknown pin: %s", pinName)
372 }
373 usedPinNames[pinName] = true
374 }
375 }
376
377 for pinName := range pinNames {
378 if _, ok := usedPinNames[pinName]; !ok {
379 return fmt.Errorf("unused pin: %s", pinName)
380 }
381 }
382
383 return nil
384 }
385
386 func writeHeader(out *bufio.Writer) {
387 out.WriteString(`// Copyright (c) 2012 The Chromium Authors. All rights reserved.
388 // Use of this source code is governed by a BSD-style license that can be
389 // found in the LICENSE file.
390
391 // This file is automatically generated by transport_security_state_static_gener ate.go
392
393 #ifndef NET_BASE_TRANSPORT_SECURITY_STATE_STATIC_H_
394 #define NET_BASE_TRANSPORT_SECURITY_STATE_STATIC_H_
395 #pragma once
396
397 `)
398
399 }
400
401 func writeFooter(out *bufio.Writer) {
402 out.WriteString("#endif // NET_BASE_TRANSPORT_SECURITY_STATE_STATIC_H_\n ")
403 }
404
405 func writeCertsOutput(out *bufio.Writer, pins []pin) {
406 out.WriteString(`// These are SubjectPublicKeyInfo hashes for public key pinning. The
407 // hashes are base64 encoded, SHA1 digests.
408
409 `)
410
411 for _, pin := range pins {
412 if pin.cert != nil {
413 out.WriteString("#if 0\n")
414 pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: p in.cert.Raw})
415 out.WriteString("#endif\n")
416 }
417 fmt.Fprintf(out, "static const char kSPKIHash_%s[] =\n", pin.nam e)
418 fmt.Fprintf(out, " \"%s/%s\";\n\n", pin.spkiHashFunc, base64. StdEncoding.EncodeToString(pin.spkiHash))
419 }
420 }
421
422 // uppercaseFirstLetter returns s with the first letter uppercased.
423 func uppercaseFirstLetter(s string) string {
424 // We need to find the index of the second code-point, which may not be
425 // one.
426 for i := range s {
427 if i == 0 {
428 continue
429 }
430 return strings.ToUpper(s[:i]) + s[i:]
431 }
432 return strings.ToUpper(s)
433 }
434
435 func writeListOfPins(w io.Writer, name string, pinNames []string) {
436 fmt.Fprintf(w, "static const char* const %s[] = {\n", name)
437 for _, pinName := range pinNames {
438 fmt.Fprintf(w, " kSPKIHash_%s,\n", pinName)
439 }
440 fmt.Fprintf(w, " NULL,\n};\n")
441 }
442
443 // toDNS returns a string converts the domain name |s| into C-escaped,
444 // length-prefixed form and also returns the length of the interpreted string.
445 // i.e. for an input "example.com" it will return "\\007example\\003com", 13.
446 func toDNS(s string) (string, int) {
447 labels := strings.Split(s, ".")
448
449 var name string
450 var l int
451 for _, label := range labels {
452 if len(label) > 63 {
453 panic("DNS label too long")
454 }
455 name += fmt.Sprintf("\\%03o", len(label))
456 name += label
457 l += len(label) + 1
458 }
459 l += 1 // For the length of the root label.
460
461 return name, l
462 }
463
464 // domainConstant converts the domain name |s| into a string of the form
465 // "DOMAIN_" + uppercase last two labels.
466 func domainConstant(s string) string {
467 labels := strings.Split(s, ".")
468 gtld := strings.ToUpper(labels[len(labels)-1])
469 domain := strings.Replace(strings.ToUpper(labels[len(labels)-2]), "-", " _", -1)
470
471 return fmt.Sprintf("DOMAIN_%s_%s", domain, gtld)
472 }
473
474 func writeHSTSEntry(out *bufio.Writer, entry hsts) {
475 dnsName, dnsLen := toDNS(entry.Name)
476 domain := "DOMAIN_NOT_PINNED"
477 pinsetName := "kNoPins"
478 if len(entry.Pins) > 0 {
479 pinsetName = fmt.Sprintf("k%sPins", uppercaseFirstLetter(entry.P ins))
480 domain = domainConstant(entry.Name)
481 }
482 fmt.Fprintf(out, " {%d, %t, \"%s\", %t, %s, %s },\n", dnsLen, entry.Sub domains, dnsName, entry.Mode == "force-https", pinsetName, domain)
483 }
484
485 func writeHSTSOutput(out *bufio.Writer, hsts preloaded) error {
486 out.WriteString(`// The following is static data describing the hosts th at are hardcoded with
487 // certificate pins or HSTS information.
488
489 // kNoRejectedPublicKeys is a placeholder for when no public keys are rejected.
490 static const char* const kNoRejectedPublicKeys[] = {
491 NULL,
492 };
493
494 `)
495
496 for _, pinset := range hsts.Pinsets {
497 name := uppercaseFirstLetter(pinset.Name)
498 acceptableListName := fmt.Sprintf("k%sAcceptableCerts", name)
499 writeListOfPins(out, acceptableListName, pinset.Include)
500
501 rejectedListName := "kNoRejectedPublicKeys"
502 if len(pinset.Exclude) > 0 {
503 rejectedListName = fmt.Sprintf("k%sRejectedCerts", name)
504 writeListOfPins(out, rejectedListName, pinset.Exclude)
505 }
506 fmt.Fprintf(out, `#define k%sPins { \
507 %s, \
508 %s, \
509 }
510
511 `, name, acceptableListName, rejectedListName)
512 }
513
514 out.WriteString(`#define kNoPins {\
515 NULL, NULL, \
516 }
517
518 static const struct HSTSPreload kPreloadedSTS[] = {
519 `)
520
521 for _, entry := range hsts.Entries {
522 if entry.SNIOnly {
523 continue
524 }
525 writeHSTSEntry(out, entry)
526 }
527
528 out.WriteString(`};
529 static const size_t kNumPreloadedSTS = ARRAYSIZE_UNSAFE(kPreloadedSTS);
530
531 static const struct HSTSPreload kPreloadedSNISTS[] = {
532 `)
533
534 for _, entry := range hsts.Entries {
535 if !entry.SNIOnly {
536 continue
537 }
538 writeHSTSEntry(out, entry)
539 }
540
541 out.WriteString(`};
542 static const size_t kNumPreloadedSNISTS = ARRAYSIZE_UNSAFE(kPreloadedSNISTS);
543
544 `)
545
546 return nil
547 }
OLDNEW
« no previous file with comments | « net/base/transport_security_state_static.json ('k') | net/net.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698