mirror of
https://wiilab.wiimart.org/wiimart/WiiMart-Patcher
synced 2025-09-04 04:21:19 +02:00
Add initial certificate patching
This commit is contained in:
commit
a3f2361ad2
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.idea
|
||||||
|
cache/
|
||||||
|
WSC-Patcher
|
||||||
|
output/
|
39
README.md
Normal file
39
README.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# WSC-Patcher
|
||||||
|
|
||||||
|
WSC-Patcher applies patches to the Wii Shop Channel, such as the ability to use your own servers and certificates.
|
||||||
|
This is useful for research and development of services utilizing the WSC.
|
||||||
|
|
||||||
|
It is important to read the following so that you will have a usable WAD available upon patch completion.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
You will need an externally resolvable domain with four subdomains:
|
||||||
|
- `oss-auth`, utilized for the Wii Shop's main HTML
|
||||||
|
- `ecs`, utilized for ticket syncing and title purchases
|
||||||
|
- `ias`, utilized for user registration
|
||||||
|
- `ccs`, utilized to download titles
|
||||||
|
|
||||||
|
The domain must be equal to or smaller than `shop.wii.com` in length, so 12 characters.
|
||||||
|
|
||||||
|
If you do not plan to interact with EC, and plan to solely utilize HTML/JS components of the Wii Shop Channel, only configuring `oss-auth` is acceptable.
|
||||||
|
|
||||||
|
If you do not have a domain available, you are welcome to utilize `a.taur.cloud` as the base domain.
|
||||||
|
This domain resolves to `127.0.0.1`, usable within Dolphin.
|
||||||
|
It is guaranteed `oss-auth`, `ecs`, `ias`, `cas` (cataloguing, within DLC titles), and `ccs`/`ucs` (cached/uncached content servers) are available.
|
||||||
|
|
||||||
|
You may choose to specify a root certificate you already have configured on a server. If so, please provide the public key of the CA within the file `output/root.crt` in DER form.
|
||||||
|
If not, one will be generated for you.
|
||||||
|
|
||||||
|
## Operation
|
||||||
|
Invoke WSC-Patcher similar to the following:
|
||||||
|
```
|
||||||
|
./WSC-Patcher <base domain>
|
||||||
|
```
|
||||||
|
|
||||||
|
Throughout its operation, the patcher will perform the following:
|
||||||
|
- Version 20 (latest, as of writing) of the Wii Shop Channel will be downloaded to `cache/original.wad`.
|
||||||
|
- If `output/ca.crt` is not present, a 2048-bit (RSA), SHA-1 CA certificate will be generated.
|
||||||
|
- At the same time, `*.<basedomain>` will be issued for ease of use.
|
||||||
|
- Modifications are made to the application's main `.arc` (within content index 2) to permit Opera loading the base domain.
|
||||||
|
- Patches to the application's main dol are also performed. Please see `patches.go` for more information on what these contain.
|
||||||
|
- The patched WAD is encrypted and written to disk.
|
||||||
|
|
101
cert_store.go
Normal file
101
cert_store.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// generateSerial generates a random serial number for our issued certificates.
|
||||||
|
// It is taken from golang std: src/crypto/tls/generate_cert.go
|
||||||
|
// Direct permalink on GitHub: https://git.io/JyyDw
|
||||||
|
func generateSerial() *big.Int {
|
||||||
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to generate serial number: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serialNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
func createCertificates() []byte {
|
||||||
|
////////////////////////////////////
|
||||||
|
// Generate root CA //
|
||||||
|
////////////////////////////////////
|
||||||
|
rootCA := x509.Certificate{
|
||||||
|
SignatureAlgorithm: x509.SHA1WithRSA,
|
||||||
|
SerialNumber: generateSerial(),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: "Open Shop Channel CA",
|
||||||
|
},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().AddDate(10, 0, 0),
|
||||||
|
KeyUsage: x509.KeyUsageCertSign,
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
IsCA: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sadly, 2048 bits can cause compatability issues with IOS. We must use 1024.
|
||||||
|
// TODO(spotlightishere): Is it possible to raise to 2048 anyway?
|
||||||
|
rootPriv, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
rootCertBytes, err := x509.CreateCertificate(rand.Reader, &rootCA, &rootCA, &rootPriv.PublicKey, rootPriv)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
////////////////////////////////////
|
||||||
|
// Issue server TLS certificate //
|
||||||
|
////////////////////////////////////
|
||||||
|
serverCert := x509.Certificate{
|
||||||
|
SignatureAlgorithm: x509.SHA1WithRSA,
|
||||||
|
SerialNumber: generateSerial(),
|
||||||
|
// We'll issue with a primary common name for our base domain.
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: baseDomain,
|
||||||
|
},
|
||||||
|
// The SAN will be a wildcard for our base domain, as it cannot be the CN.
|
||||||
|
DNSNames: []string{
|
||||||
|
"*." + baseDomain,
|
||||||
|
},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().AddDate(10, 0, 0),
|
||||||
|
// TODO: what's non-repudiation
|
||||||
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
IsCA: false,
|
||||||
|
MaxPathLenZero: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
serverPriv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
serverCertBytes, err := x509.CreateCertificate(rand.Reader, &serverCert, &rootCA, &rootPriv.PublicKey, serverPriv)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
////////////////////////////
|
||||||
|
// Persist certificates //
|
||||||
|
////////////////////////////
|
||||||
|
rootCertPem := pemEncode("CERTIFICATE", rootCertBytes)
|
||||||
|
rootKeyPem := pemEncode("RSA PRIVATE KEY", x509.MarshalPKCS1PrivateKey(rootPriv))
|
||||||
|
serverCertPem := pemEncode("CERTIFICATE", serverCertBytes)
|
||||||
|
serverKeyPem := pemEncode("RSA PRIVATE KEY", x509.MarshalPKCS1PrivateKey(serverPriv))
|
||||||
|
|
||||||
|
writeOut("root.pem", rootCertPem)
|
||||||
|
writeOut("root.cer", rootCertBytes)
|
||||||
|
writeOut("root.key", rootKeyPem)
|
||||||
|
writeOut("server.pem", serverCertPem)
|
||||||
|
writeOut("server.key", serverKeyPem)
|
||||||
|
|
||||||
|
return rootCertBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func pemEncode(typeName string, bytes []byte) []byte {
|
||||||
|
block := pem.Block{Type: typeName, Bytes: bytes}
|
||||||
|
return pem.EncodeToMemory(&block)
|
||||||
|
}
|
6
docs/README.md
Normal file
6
docs/README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Docs
|
||||||
|
This directory contains documentation about given patches applied.
|
||||||
|
|
||||||
|
Contents:
|
||||||
|
- `opcacrt6.yml`: A [Kaitai](https://kaitai.io) structure describing a very basic `opcacrt6.dat`.
|
||||||
|
It does not attempt to handle things such as client certificates or user passwords.
|
75
docs/opcacrt6.yml
Normal file
75
docs/opcacrt6.yml
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
meta:
|
||||||
|
id: dat
|
||||||
|
file-extension: dat
|
||||||
|
endian: be
|
||||||
|
# Derived from
|
||||||
|
# https://web.archive.org/web/20090208111457/http://www.opera.com/docs/fileformats/
|
||||||
|
seq:
|
||||||
|
- id: file_version_number
|
||||||
|
type: u4
|
||||||
|
- id: app_version_number
|
||||||
|
type: u4
|
||||||
|
- id: idtag_length
|
||||||
|
# Asserted to always have a tag length of 1.
|
||||||
|
contents: [0x00, 0x01]
|
||||||
|
- id: length_length
|
||||||
|
# Asserted to always have a length of 4.
|
||||||
|
contents: [0x00, 0x04]
|
||||||
|
- id: tags
|
||||||
|
type: tag
|
||||||
|
# repeat: eos
|
||||||
|
|
||||||
|
types:
|
||||||
|
tag:
|
||||||
|
seq:
|
||||||
|
- id: tag_type
|
||||||
|
type: u1
|
||||||
|
enum: tag_types
|
||||||
|
- id: tag_size
|
||||||
|
type: u4
|
||||||
|
- id: tag_contents
|
||||||
|
type:
|
||||||
|
switch-on: tag_type
|
||||||
|
cases:
|
||||||
|
'tag_types::ca_certificate': ca_tag
|
||||||
|
|
||||||
|
# All of this should technically not be sequential, but it always is for our purposes.
|
||||||
|
# If you wish to adapt this for other Opera usage, please adjust accordingly!
|
||||||
|
ca_tag:
|
||||||
|
seq:
|
||||||
|
- id: cert_type_tag
|
||||||
|
# 'tag_types::ssl_cert_type'
|
||||||
|
contents: [0x20]
|
||||||
|
- id: cert_type_length
|
||||||
|
type: u4
|
||||||
|
- id: cert_type
|
||||||
|
type: u4
|
||||||
|
- id: cert_name_tag
|
||||||
|
contents: [0x21]
|
||||||
|
- id: cert_name_length
|
||||||
|
type: u4
|
||||||
|
- id: cert_name
|
||||||
|
type: str
|
||||||
|
size: cert_name_length
|
||||||
|
encoding: UTF-8
|
||||||
|
- id: cert_subject_tag
|
||||||
|
contents: [0x22]
|
||||||
|
- id: cert_subject_length
|
||||||
|
type: u4
|
||||||
|
- id: cert_subject
|
||||||
|
size: cert_subject_length
|
||||||
|
- id: cert_contents_tag
|
||||||
|
contents: [0x23]
|
||||||
|
- id: cert_contents_length
|
||||||
|
type: u4
|
||||||
|
- id: cert_contents
|
||||||
|
size: cert_contents_length
|
||||||
|
|
||||||
|
enums:
|
||||||
|
tag_types:
|
||||||
|
0x02: ca_certificate
|
||||||
|
0x03: user_certificate
|
||||||
|
0x04: user_password
|
||||||
|
0x20: ssl_cert_type
|
||||||
|
0x21: ssl_cert_name
|
||||||
|
0x22: ssl_cert_subject
|
9
go.mod
Normal file
9
go.mod
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
module github.com/OpenShopChannel/WSC-Patcher
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/wii-tools/GoNUSD v0.2.1
|
||||||
|
github.com/wii-tools/arclib v1.0.0
|
||||||
|
github.com/wii-tools/wadlib v0.3.0
|
||||||
|
)
|
6
go.sum
Normal file
6
go.sum
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
github.com/wii-tools/GoNUSD v0.2.1 h1:HW8vssrEiCdFjiugCx3cTTaPw9nlz1TWxzeCqXKqTnQ=
|
||||||
|
github.com/wii-tools/GoNUSD v0.2.1/go.mod h1:jMYMU5X81Hp0R+bq6/0AxAOijGHXfexaG6dLRilgS2s=
|
||||||
|
github.com/wii-tools/arclib v1.0.0 h1:OAmbL3NDUmlR0wa1VpJhxnKiIRFZz1CC40lTVPUm8Ms=
|
||||||
|
github.com/wii-tools/arclib v1.0.0/go.mod h1:uXFan/NSXoQ2pOVPN4ugZ4nJX7esBnjB1QUgVrEzK/4=
|
||||||
|
github.com/wii-tools/wadlib v0.3.0 h1:ZPCjuiwn9AG2og5paVeP7eH3nfjWC2iL4u8WW5nOJ4U=
|
||||||
|
github.com/wii-tools/wadlib v0.3.0/go.mod h1:GK+f2POk+rVu1p4xqLSb4ll1SKKbfOO6ZAB+oPLV3uQ=
|
122
main.go
Normal file
122
main.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/wii-tools/GoNUSD"
|
||||||
|
"github.com/wii-tools/arclib"
|
||||||
|
"github.com/wii-tools/wadlib"
|
||||||
|
"io/fs"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// baseDomain holds our needed base domain.
|
||||||
|
var baseDomain string
|
||||||
|
|
||||||
|
// mainDol holds the main DOL - our content at index 1.
|
||||||
|
var mainDol []byte
|
||||||
|
|
||||||
|
// mainArc holds the main ARC - our content at index 2.
|
||||||
|
var mainArc *arclib.ARC
|
||||||
|
|
||||||
|
// filePresent returns whether the specified path is present on disk.
|
||||||
|
func filePresent(path string) bool {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
return errors.Is(err, fs.ErrNotExist) == false
|
||||||
|
}
|
||||||
|
|
||||||
|
// createDir creates a directory at the given path if it is not already present.
|
||||||
|
func createDir(path string) {
|
||||||
|
if !filePresent(path) {
|
||||||
|
os.Mkdir(path, 0755)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 2 {
|
||||||
|
fmt.Printf("Usage: %s <base domain>\n", os.Args[0])
|
||||||
|
fmt.Println("For more information, please refer to the README.")
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseDomain = os.Args[1]
|
||||||
|
if len(baseDomain) > 12 {
|
||||||
|
fmt.Println("The given base domain must not exceed 12 characters.")
|
||||||
|
fmt.Println("For more information, please refer to the README.")
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("===========================")
|
||||||
|
fmt.Println("= WSC-Patcher =")
|
||||||
|
fmt.Println("===========================")
|
||||||
|
|
||||||
|
// Create directories we may need later.
|
||||||
|
createDir("./output")
|
||||||
|
createDir("./cache")
|
||||||
|
|
||||||
|
var originalWad *wadlib.WAD
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Determine whether the Wii Shop Channel is cached.
|
||||||
|
if !filePresent("./cache/original.wad") {
|
||||||
|
log.Println("Downloading a copy of the original Wii Shop Channel, please wait...")
|
||||||
|
originalWad, err = GoNUSD.Download(0x00010002_48414241, 21, true)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
// Cache this downloaded WAD to disk.
|
||||||
|
contents, err := originalWad.GetWAD(wadlib.WADTypeCommon)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
os.WriteFile("./cache/original.wad", contents, 0755)
|
||||||
|
} else {
|
||||||
|
originalWad, err = wadlib.LoadWADFromFile("./cache/original.wad")
|
||||||
|
check(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine whether a certificate authority was provided, or generated previously.
|
||||||
|
if !filePresent("./output/root.cer") {
|
||||||
|
log.Println("Generating root certificates...")
|
||||||
|
createCertificates()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load main DOL
|
||||||
|
mainDol, err = originalWad.GetContent(1)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
// Load main ARC
|
||||||
|
arcData, err := originalWad.GetContent(2)
|
||||||
|
check(err)
|
||||||
|
mainArc, err = arclib.Load(arcData)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
// Generate filter list and certificate store
|
||||||
|
log.Println("Applying Opera patches...")
|
||||||
|
modifyAllowList()
|
||||||
|
generateOperaCertStore()
|
||||||
|
|
||||||
|
// Save main ARC
|
||||||
|
updated, err := mainArc.Save()
|
||||||
|
check(err)
|
||||||
|
err = originalWad.UpdateContent(2, updated)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
// Generate a patched WAD with our changes
|
||||||
|
output, err := originalWad.GetWAD(wadlib.WADTypeCommon)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
log.Println("Done! Install ./output/patched.wad, sit back, and enjoy.")
|
||||||
|
writeOut("patched.wad", output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check has an anxiety attack if things go awry.
|
||||||
|
func check(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeOut writes a file with the given name and contents to the output folder.
|
||||||
|
func writeOut(filename string, contents []byte) {
|
||||||
|
os.WriteFile("./output/"+filename, contents, 0755)
|
||||||
|
}
|
125
modify_arc.go
Normal file
125
modify_arc.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// modifyAllowList patches the Opera filter to include our custom base domain.
|
||||||
|
func modifyAllowList() {
|
||||||
|
file, err := mainArc.OpenFile("arc/opera/myfilter.ini")
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
// TODO(spotlightishere): Find an INI parser that handles reading an array from a section
|
||||||
|
// As I could not - and I spent a good while looking - and do not want to implement my own parser,
|
||||||
|
// no matter how rudimentary - I've copied the original file as a template verbatim. We only make one edit,
|
||||||
|
// adding the base domain.
|
||||||
|
filter := fmt.Sprintf(`[prefs]
|
||||||
|
prioritize excludelist=0
|
||||||
|
|
||||||
|
[include]
|
||||||
|
file:/cnt/*
|
||||||
|
https://*.%s/*
|
||||||
|
miip:*
|
||||||
|
|
||||||
|
[exclude]
|
||||||
|
*`, baseDomain)
|
||||||
|
|
||||||
|
file.Write([]byte(filter))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag represents a single byte representing a tag's ID.
|
||||||
|
type Tag byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
TagSSLCertType = 0x20
|
||||||
|
TagSSLCertName = 0x21
|
||||||
|
TagSSLCertSubject = 0x22
|
||||||
|
TagSSLCertContents = 0x23
|
||||||
|
|
||||||
|
TagCACertificate = 0x02
|
||||||
|
TagUserCertificate = 0x03
|
||||||
|
TagUserPassword = 0x04
|
||||||
|
)
|
||||||
|
|
||||||
|
// generateTag generates a byte representation of a tag and contents.
|
||||||
|
func generateTag(tag Tag, tagContents []byte) []byte {
|
||||||
|
// Tag ID
|
||||||
|
contents := []byte{
|
||||||
|
byte(tag),
|
||||||
|
}
|
||||||
|
// Tag length
|
||||||
|
contents = append(contents, toLength(len(tagContents))...)
|
||||||
|
// Tag contents
|
||||||
|
contents = append(contents, tagContents...)
|
||||||
|
|
||||||
|
return contents
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateOperaCertStore creates our own custom Opera cert store for the given certificate.
|
||||||
|
func generateOperaCertStore() {
|
||||||
|
file, err := mainArc.OpenFile("arc/opera/opcacrt6.dat")
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
// Load our existing root certificate in DER form.
|
||||||
|
rootCertContents, err := ioutil.ReadFile("./output/root.cer")
|
||||||
|
check(err)
|
||||||
|
rootCert, err := x509.ParseCertificate(rootCertContents)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
// The following array was done manually after several hours of tinkering.
|
||||||
|
// Please refer to docs/opcacrt6.yml for more about the structure of this file.
|
||||||
|
// TODO(spotlightishere): Is it possible to somehow generate a structure for easier access?
|
||||||
|
header := []byte{
|
||||||
|
// File version number
|
||||||
|
0x00, 0x00, 0x10, 0x00,
|
||||||
|
// App version number
|
||||||
|
0x05, 0x05, 0x00, 0x23,
|
||||||
|
// ID tag "length" - always one byte
|
||||||
|
0x00, 0x01,
|
||||||
|
// Length field byte length - always four bytes
|
||||||
|
0x00, 0x04,
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's unclear on what 0x01 is supposed to represent,
|
||||||
|
// but it must be a CA certificate.
|
||||||
|
certTypeTag := generateTag(TagSSLCertType, []byte{
|
||||||
|
0x0, 0x0, 0x0, 0x1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// We can obtain the name and subject from the root certificate.
|
||||||
|
certNameTag := generateTag(TagSSLCertName, []byte(rootCert.Subject.CommonName))
|
||||||
|
certSubjectTag := generateTag(TagSSLCertSubject, rootCert.RawSubject)
|
||||||
|
|
||||||
|
// Finally, our actual certificate.
|
||||||
|
certContentsTag := generateTag(TagSSLCertContents, rootCertContents)
|
||||||
|
|
||||||
|
// We must enclose our type, name, subject and contents tag in a CA certificate tag.
|
||||||
|
bundledContents := append(certTypeTag, certNameTag...)
|
||||||
|
bundledContents = append(bundledContents, certSubjectTag...)
|
||||||
|
bundledContents = append(bundledContents, certContentsTag...)
|
||||||
|
|
||||||
|
caCertTag := generateTag(TagCACertificate, bundledContents)
|
||||||
|
|
||||||
|
// Thankfully, that is all.
|
||||||
|
// In the end, we have a structure similar to the following:
|
||||||
|
// - header (file/app version, id/length byte length)
|
||||||
|
// - ca certificate
|
||||||
|
// - id
|
||||||
|
// - length
|
||||||
|
// - value:
|
||||||
|
// - type tag (id, length, value)
|
||||||
|
// - type name (id, length, value)
|
||||||
|
// - type subject (id, length, value)
|
||||||
|
// - type contents (id, length, value)
|
||||||
|
file.Write(append(header, caCertTag...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// toLength returns 4 bytes, suitable for the given length.
|
||||||
|
func toLength(value int) []byte {
|
||||||
|
holder := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(holder, uint32(value))
|
||||||
|
return holder
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user