summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStuart Clark <stuart.clark@Jahingo.com>2017-08-08 02:20:56 +0100
committerDave Henderson <dhenderson@gmail.com>2017-08-07 21:20:56 -0400
commitd2cf55b83fe71d41c3a09b35b280d9a48b24088d (patch)
treecc152e6ed7d4f2346d179bb867a683e47a3d9852
parentcbc2af15fccdfc43ae6dfc2ae2d88248df039ddb (diff)
Vault AWS EC2 auth (#190)
-rw-r--r--Gopkg.lock8
-rw-r--r--Makefile26
-rw-r--r--aws/ec2meta.go8
-rw-r--r--docs/content/functions/general.md11
-rw-r--r--test/integration/.gitignore2
-rw-r--r--test/integration/Dockerfile4
-rw-r--r--test/integration/awssvc/main.go190
-rw-r--r--test/integration/datasources_vault.bats21
-rw-r--r--test/integration/helper.bash18
-rw-r--r--test/integration/metasvc/main.go142
-rw-r--r--vault/auth.go42
-rw-r--r--vendor/github.com/fullsailor/pkcs7/.gitignore24
-rw-r--r--vendor/github.com/fullsailor/pkcs7/.travis.yml6
-rw-r--r--vendor/github.com/fullsailor/pkcs7/LICENSE22
-rw-r--r--vendor/github.com/fullsailor/pkcs7/README.md8
-rw-r--r--vendor/github.com/fullsailor/pkcs7/ber.go248
-rw-r--r--vendor/github.com/fullsailor/pkcs7/ber_test.go97
-rw-r--r--vendor/github.com/fullsailor/pkcs7/pkcs7.go940
-rw-r--r--vendor/github.com/fullsailor/pkcs7/pkcs7_test.go678
19 files changed, 2489 insertions, 6 deletions
diff --git a/Gopkg.lock b/Gopkg.lock
index 8f0c03b5..6de6de9f 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -38,6 +38,12 @@
version = "v1.0"
[[projects]]
+ branch = "master"
+ name = "github.com/fullsailor/pkcs7"
+ packages = ["."]
+ revision = "a009d8d7de53d9503c797cb8ec66fa3b21eed209"
+
+[[projects]]
name = "github.com/go-ini/ini"
packages = ["."]
revision = "d3de07a94d22b4a0972deb4b96d790c2c0ce8333"
@@ -184,6 +190,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
- inputs-digest = "3f83e366940e70c240f81a09a042d5a283b02efadd0ad06d0847ee7ad6ee852a"
+ inputs-digest = "31a8e0b536f3b7bdc59f2f911ba5060012b81357402532c1525544065f9d02ea"
solver-name = "gps-cdcl"
solver-version = 1
diff --git a/Makefile b/Makefile
index 54ad02e0..b390b888 100644
--- a/Makefile
+++ b/Makefile
@@ -38,6 +38,8 @@ clean:
rm -Rf $(PREFIX)/bin/*
rm -f $(PREFIX)/test/integration/gomplate
rm -f $(PREFIX)/test/integration/mirror
+ rm -f $(PREFIX)/test/integration/meta
+ rm -f $(PREFIX)/test/integration/aws
build-x: $(patsubst %,$(PREFIX)/bin/$(PKG_NAME)_%,$(platforms))
@@ -58,6 +60,12 @@ $(PREFIX)/bin/$(PKG_NAME)_%: $(shell find $(PREFIX) -type f -name '*.go' -not -p
$(PREFIX)/bin/mirror_%: $(shell find $(PREFIX)/test/integration/mirrorsvc -type f -name '*.go')
$(call gocross-tool,$(shell echo $* | sed 's/\([^-]*\)-\([^.]*\).*/\1/'),$(shell echo $* | sed 's/\([^-]*\)-\([^.]*\).*/\2/'),mirror)
+$(PREFIX)/bin/meta_%: $(shell find $(PREFIX)/test/integration/metasvc -type f -name '*.go')
+ $(call gocross-tool,$(shell echo $* | sed 's/\([^-]*\)-\([^.]*\).*/\1/'),$(shell echo $* | sed 's/\([^-]*\)-\([^.]*\).*/\2/'),meta)
+
+$(PREFIX)/bin/aws_%: $(shell find $(PREFIX)/test/integration/awssvc -type f -name '*.go')
+ $(call gocross-tool,$(shell echo $* | sed 's/\([^-]*\)-\([^.]*\).*/\1/'),$(shell echo $* | sed 's/\([^-]*\)-\([^.]*\).*/\2/'),aws)
+
$(PREFIX)/bin/$(PKG_NAME)$(call extension,$(GOOS)): $(shell find $(PREFIX) -type f -name '*.go' -not -path "$(PREFIX)/test/*")
CGO_ENABLED=0 \
$(GO) build -ldflags "-w -s $(COMMIT_FLAG) $(VERSION_FLAG)" -o $@
@@ -66,22 +74,36 @@ $(PREFIX)/bin/mirror$(call extension,$(GOOS)): $(shell find $(PREFIX)/test/integ
CGO_ENABLED=0 \
$(GO) build -ldflags "-w -s $(COMMIT_FLAG) $(VERSION_FLAG)" -o $@ $(PREFIX)/test/integration/mirrorsvc
+$(PREFIX)/bin/meta$(call extension,$(GOOS)): $(shell find $(PREFIX)/test/integration/metasvc -type f -name '*.go')
+ CGO_ENABLED=0 \
+ $(GO) build -ldflags "-w -s $(COMMIT_FLAG) $(VERSION_FLAG)" -o $@ $(PREFIX)/test/integration/metasvc
+
+$(PREFIX)/bin/aws$(call extension,$(GOOS)): $(shell find $(PREFIX)/test/integration/awssvc -type f -name '*.go')
+ CGO_ENABLED=0 \
+ $(GO) build -ldflags "-w -s $(COMMIT_FLAG) $(VERSION_FLAG)" -o $@ $(PREFIX)/test/integration/awssvc
+
build: $(PREFIX)/bin/$(PKG_NAME)$(call extension,$(GOOS))
build-mirror: $(PREFIX)/bin/mirror$(call extension,$(GOOS))
+build-meta: $(PREFIX)/bin/meta$(call extension,$(GOOS))
+
+build-aws: $(PREFIX)/bin/aws$(call extension,$(GOOS))
+
test:
$(GO) test -v -race `glide novendor`
-build-integration-image: $(PREFIX)/bin/$(PKG_NAME)_linux-amd64$(call extension,$(GOOS)) $(PREFIX)/bin/mirror_linux-amd64$(call extension,$(GOOS))
+build-integration-image: $(PREFIX)/bin/$(PKG_NAME)_linux-amd64$(call extension,$(GOOS)) $(PREFIX)/bin/mirror_linux-amd64$(call extension,$(GOOS)) $(PREFIX)/bin/meta_linux-amd64$(call extension,$(GOOS)) $(PREFIX)/bin/aws_linux-amd64$(call extension,$(GOOS))
cp $(PREFIX)/bin/$(PKG_NAME)_linux-amd64 test/integration/gomplate
cp $(PREFIX)/bin/mirror_linux-amd64 test/integration/mirror
+ cp $(PREFIX)/bin/meta_linux-amd64 test/integration/meta
+ cp $(PREFIX)/bin/aws_linux-amd64 test/integration/aws
docker build -f test/integration/Dockerfile -t gomplate-test test/integration/
test-integration-docker: build-integration-image
docker run -it --rm gomplate-test
-test-integration: build build-mirror
+test-integration: build build-mirror build-meta build-aws
@test/integration/test.sh
gen-changelog:
diff --git a/aws/ec2meta.go b/aws/ec2meta.go
index 0e5cba64..cc3766b7 100644
--- a/aws/ec2meta.go
+++ b/aws/ec2meta.go
@@ -7,10 +7,12 @@ import (
"net/http"
"strings"
"time"
+
+ "github.com/hairyhenderson/gomplate/env"
)
// DefaultEndpoint -
-const DefaultEndpoint = "http://169.254.169.254"
+var DefaultEndpoint = "http://169.254.169.254"
// Ec2Meta -
type Ec2Meta struct {
@@ -23,6 +25,10 @@ type Ec2Meta struct {
// NewEc2Meta -
func NewEc2Meta(options ClientOptions) *Ec2Meta {
+ if endpoint := env.Getenv("AWS_META_ENDPOINT"); endpoint != "" {
+ DefaultEndpoint = endpoint
+ }
+
return &Ec2Meta{cache: make(map[string]string), options: options}
}
diff --git a/docs/content/functions/general.md b/docs/content/functions/general.md
index 28f518bd..c3c2cfe0 100644
--- a/docs/content/functions/general.md
+++ b/docs/content/functions/general.md
@@ -593,6 +593,7 @@ This table describes the currently-supported authentication mechanisms and how t
| [`github`](https://www.vaultproject.io/docs/auth/github.html) | Environment variable `$VAULT_AUTH_GITHUB_TOKEN` must be set to an appropriate value.<br/> If the backend is mounted to a different location, set `$VAULT_AUTH_GITHUB_MOUNT`. |
| [`userpass`](https://www.vaultproject.io/docs/auth/userpass.html) | Environment variables `$VAULT_AUTH_USERNAME` and `$VAULT_AUTH_PASSWORD` must be set to the appropriate values.<br/> If the backend is mounted to a different location, set `$VAULT_AUTH_USERPASS_MOUNT`. |
| [`token`](https://www.vaultproject.io/docs/auth/token.html) | Determined from either the `$VAULT_TOKEN` environment variable, or read from the file `~/.vault-token` |
+| [`aws`](https://www.vaultproject.io/docs/auth/aws.html) | As a final option authentication will be attempted using the AWS auth backend. See below for more details. |
_**Note:**_ The secret values listed in the above table can either be set in environment
variables or provided in files. This can increase security when using
@@ -637,6 +638,16 @@ $ echo 'otp={{(datasource "vault" "ssh/creds/test?ip=10.1.2.3&username=user").ke
otp=604a4bd5-7afd-30a2-d2d8-80c4aebc6183
```
+#### Authentication using AWS details
+
+If running on an EC2 instance authentication will be attempted using the AWS auth backend. The
+optional `VAULT_AUTH_AWS_MOUNT` environment variable can be used to set the mount point to use if
+it differs from the default of `aws`. Additionally `AWS_TIMEOUT` can be set (in seconds) to a value
+to wait for AWS to respond before skipping the attempt.
+
+If set, the `VAULT_AUTH_AWS_ROLE` environment variable will be used to specify the role to authenticate
+using. If not set the AMI ID of the EC2 instance will be used by Vault.
+
## `datasourceExists`
Tests whether or not a given datasource was defined on the commandline (with the
diff --git a/test/integration/.gitignore b/test/integration/.gitignore
index 9c2b49ab..62c29879 100644
--- a/test/integration/.gitignore
+++ b/test/integration/.gitignore
@@ -1,2 +1,4 @@
gomplate
mirror
+meta
+aws
diff --git a/test/integration/Dockerfile b/test/integration/Dockerfile
index e8cbc2ce..894a04a7 100644
--- a/test/integration/Dockerfile
+++ b/test/integration/Dockerfile
@@ -1,6 +1,6 @@
FROM alpine:edge
-ENV VAULT_VER 0.7.0
+ENV VAULT_VER 0.7.3
ENV CONSUL_VER 0.9.0
RUN apk add --no-cache \
curl \
@@ -21,6 +21,8 @@ RUN mkdir /lib64 \
COPY gomplate /bin/gomplate
COPY mirror /bin/mirror
+COPY meta /bin/meta
+COPY aws /bin/aws
COPY *.sh /tests/
COPY *.bash /tests/
COPY *.bats /tests/
diff --git a/test/integration/awssvc/main.go b/test/integration/awssvc/main.go
new file mode 100644
index 00000000..0a3f8f05
--- /dev/null
+++ b/test/integration/awssvc/main.go
@@ -0,0 +1,190 @@
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "log"
+ "net"
+ "net/http"
+)
+
+// Req -
+type Req struct {
+ Headers http.Header `json:"headers"`
+}
+
+var port string
+
+func main() {
+ flag.StringVar(&port, "p", "8082", "Port to listen to")
+ flag.Parse()
+
+ l, err := net.Listen("tcp", ":"+port)
+ if err != nil {
+ log.Fatal(err)
+ }
+ // defer l.Close()
+ http.HandleFunc("/", rootHandler)
+
+ http.HandleFunc("/sts/", stsHandler)
+ http.HandleFunc("/ec2/", ec2Handler)
+ http.HandleFunc("/quit", quitHandler(l))
+
+ http.Serve(l, nil)
+}
+
+func rootHandler(w http.ResponseWriter, r *http.Request) {
+ req := Req{r.Header}
+ b, err := json.Marshal(req)
+ if err != nil {
+ log.Println(err)
+ w.WriteHeader(http.StatusBadRequest)
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.Write(b)
+}
+
+func stsHandler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/xml")
+ w.Write([]byte(`<GetCallerIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
+ <GetCallerIdentityResult>
+ <Arn>arn:aws:iam::1:user/Test</Arn>
+ <UserId>AKIAI44QH8DHBEXAMPLE</UserId>
+ <Account>1</Account>
+ </GetCallerIdentityResult>
+ <ResponseMetadata>
+ <RequestId>01234567-89ab-cdef-0123-456789abcdef</RequestId>
+ </ResponseMetadata>
+</GetCallerIdentityResponse>`))
+}
+
+func ec2Handler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/xml")
+ w.Write([]byte(`<DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
+ <requestId>8f7724cf-496f-496e-8fe3-example</requestId>
+ <reservationSet>
+ <item>
+ <reservationId>r-1234567890abcdef0</reservationId>
+ <ownerId>123456789012</ownerId>
+ <groupSet/>
+ <instancesSet>
+ <item>
+ <instanceId>i-00000000000000000</instanceId>
+ <imageId>ami-00000000</imageId>
+ <instanceState>
+ <code>16</code>
+ <name>running</name>
+ </instanceState>
+ <privateDnsName>ip-192-168-1-88.eu-west-1.compute.internal</privateDnsName>
+ <dnsName>ec2-54-194-252-215.eu-west-1.compute.amazonaws.com</dnsName>
+ <reason/>
+ <keyName>my_keypair</keyName>
+ <amiLaunchIndex>0</amiLaunchIndex>
+ <productCodes/>
+ <instanceType>t2.micro</instanceType>
+ <launchTime>2015-12-22T10:44:05.000Z</launchTime>
+ <placement>
+ <availabilityZone>eu-west-1c</availabilityZone>
+ <groupName/>
+ <tenancy>default</tenancy>
+ </placement>
+ <monitoring>
+ <state>disabled</state>
+ </monitoring>
+ <subnetId>subnet-56f5f633</subnetId>
+ <vpcId>vpc-11112222</vpcId>
+ <privateIpAddress>192.168.1.88</privateIpAddress>
+ <ipAddress>54.194.252.215</ipAddress>
+ <sourceDestCheck>true</sourceDestCheck>
+ <groupSet>
+ <item>
+ <groupId>sg-e4076980</groupId>
+ <groupName>SecurityGroup1</groupName>
+ </item>
+ </groupSet>
+ <architecture>x86_64</architecture>
+ <rootDeviceType>ebs</rootDeviceType>
+ <rootDeviceName>/dev/xvda</rootDeviceName>
+ <blockDeviceMapping>
+ <item>
+ <deviceName>/dev/xvda</deviceName>
+ <ebs>
+ <volumeId>vol-1234567890abcdef0</volumeId>
+ <status>attached</status>
+ <attachTime>2015-12-22T10:44:09.000Z</attachTime>
+ <deleteOnTermination>true</deleteOnTermination>
+ </ebs>
+ </item>
+ </blockDeviceMapping>
+ <virtualizationType>hvm</virtualizationType>
+ <clientToken>xMcwG14507example</clientToken>
+ <tagSet>
+ <item>
+ <key>Name</key>
+ <value>Server_1</value>
+ </item>
+ </tagSet>
+ <hypervisor>xen</hypervisor>
+ <networkInterfaceSet>
+ <item>
+ <networkInterfaceId>eni-551ba033</networkInterfaceId>
+ <subnetId>subnet-56f5f633</subnetId>
+ <vpcId>vpc-11112222</vpcId>
+ <description>Primary network interface</description>
+ <ownerId>123456789012</ownerId>
+ <status>in-use</status>
+ <macAddress>02:dd:2c:5e:01:69</macAddress>
+ <privateIpAddress>192.168.1.88</privateIpAddress>
+ <privateDnsName>ip-192-168-1-88.eu-west-1.compute.internal</privateDnsName>
+ <sourceDestCheck>true</sourceDestCheck>
+ <groupSet>
+ <item>
+ <groupId>sg-e4076980</groupId>
+ <groupName>SecurityGroup1</groupName>
+ </item>
+ </groupSet>
+ <attachment>
+ <attachmentId>eni-attach-39697adc</attachmentId>
+ <deviceIndex>0</deviceIndex>
+ <status>attached</status>
+ <attachTime>2015-12-22T10:44:05.000Z</attachTime>
+ <deleteOnTermination>true</deleteOnTermination>
+ </attachment>
+ <association>
+ <publicIp>54.194.252.215</publicIp>
+ <publicDnsName>ec2-54-194-252-215.eu-west-1.compute.amazonaws.com</publicDnsName>
+ <ipOwnerId>amazon</ipOwnerId>
+ </association>
+ <privateIpAddressesSet>
+ <item>
+ <privateIpAddress>192.168.1.88</privateIpAddress>
+ <privateDnsName>ip-192-168-1-88.eu-west-1.compute.internal</privateDnsName>
+ <primary>true</primary>
+ <association>
+ <publicIp>54.194.252.215</publicIp>
+ <publicDnsName>ec2-54-194-252-215.eu-west-1.compute.amazonaws.com</publicDnsName>
+ <ipOwnerId>amazon</ipOwnerId>
+ </association>
+ </item>
+ </privateIpAddressesSet>
+ <ipv6AddressesSet>
+ <item>
+ <ipv6Address>2001:db8:1234:1a2b::123</ipv6Address>
+ </item>
+ </ipv6AddressesSet>
+ </item>
+ </networkInterfaceSet>
+ <ebsOptimized>false</ebsOptimized>
+ </item>
+ </instancesSet>
+ </item>
+ </reservationSet>
+</DescribeInstancesResponse>`))
+}
+
+func quitHandler(l net.Listener) func(http.ResponseWriter, *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ l.Close()
+ w.WriteHeader(http.StatusNoContent)
+ }
+}
diff --git a/test/integration/datasources_vault.bats b/test/integration/datasources_vault.bats
index aa6e27f3..90d1a3cc 100644
--- a/test/integration/datasources_vault.bats
+++ b/test/integration/datasources_vault.bats
@@ -15,9 +15,15 @@ path "*" {
}
EOF
tmpdir=$(mktemp -d)
+ cp ~/.vault-token ~/.vault-token.bak
+ start_meta_svc
+ start_aws_svc
}
function teardown () {
+ mv ~/.vault-token.bak ~/.vault-token
+ stop_meta_svc
+ stop_aws_svc
rm -rf $tmpdir
unset VAULT_TOKEN
vault delete secret/foo
@@ -27,6 +33,7 @@ function teardown () {
vault auth-disable approle2
vault auth-disable app-id
vault auth-disable app-id2
+ vault auth-disable aws
vault policy-delete writepol
vault policy-delete readpol
vault unmount ssh
@@ -122,6 +129,20 @@ function teardown () {
[[ "${output}" == "$BATS_TEST_DESCRIPTION" ]]
}
+@test "Testing ec2 vault auth" {
+ vault write secret/foo value="$BATS_TEST_DESCRIPTION"
+ vault auth-enable aws
+ vault write auth/aws/config/client secret_key=secret access_key=access endpoint=http://127.0.0.1:8082/ec2 iam_endpoint=http://127.0.0.1:8082/iam sts_endpoint=http://127.0.0.1:8082/sts
+ curl -o $tmpdir/certificate -s -f http://127.0.0.1:8081/certificate
+ vault write auth/aws/config/certificate/testcert type=pkcs7 aws_public_cert=@$tmpdir/certificate
+ vault write auth/aws/role/ami-00000000 auth_type=ec2 bound_ami_id=ami-00000000 policies=readpol
+ unset VAULT_TOKEN
+ rm ~/.vault-token
+ AWS_META_ENDPOINT=http://127.0.0.1:8081 gomplate -d vault=vault:///secret -i '{{(datasource "vault" "foo").value}}'
+ [ "$status" -eq 0 ]
+ [[ "${output}" == "$BATS_TEST_DESCRIPTION" ]]
+}
+
@test "Testing vault auth with dynamic secret" {
vault mount ssh
vault write ssh/roles/test key_type=otp default_user=user cidr_list=10.0.0.0/8
diff --git a/test/integration/helper.bash b/test/integration/helper.bash
index 41c2ac88..00867bc8 100644
--- a/test/integration/helper.bash
+++ b/test/integration/helper.bash
@@ -34,6 +34,22 @@ function stop_mirror_svc () {
wget -q http://127.0.0.1:8080/quit
}
+function start_meta_svc () {
+ bin/meta 3>/dev/null &
+}
+
+function stop_meta_svc () {
+ wget -q http://127.0.0.1:8081/quit
+}
+
+function start_aws_svc () {
+ bin/aws &
+}
+
+function stop_aws_svc () {
+ wget -q http://127.0.0.1:8082/quit
+}
+
function wait_for_url () {
url=$1
for i in {0..10}; do
@@ -55,4 +71,4 @@ function start_consul () {
function stop_consul () {
PID_FILE=/tmp/gomplate-test-consul.pid
kill $(cat $PID_FILE) &>/dev/null
-} \ No newline at end of file
+}
diff --git a/test/integration/metasvc/main.go b/test/integration/metasvc/main.go
new file mode 100644
index 00000000..4a1d2626
--- /dev/null
+++ b/test/integration/metasvc/main.go
@@ -0,0 +1,142 @@
+package main
+
+import (
+ "bytes"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/pem"
+ "flag"
+ "log"
+ "math/big"
+ "net"
+ "net/http"
+
+ "github.com/fullsailor/pkcs7"
+)
+
+var port string
+var priv *rsa.PrivateKey
+var derBytes []byte
+
+const instanceDocument = `{
+ "devpayProductCodes" : null,
+ "availabilityZone" : "xx-test-1b",
+ "privateIp" : "10.1.2.3",
+ "version" : "2010-08-31",
+ "instanceId" : "i-00000000000000000",
+ "billingProducts" : null,
+ "instanceType" : "t2.micro",
+ "accountId" : "1",
+ "imageId" : "ami-00000000",
+ "pendingTime" : "2000-00-01T0:00:00Z",
+ "architecture" : "x86_64",
+ "kernelId" : null,
+ "ramdiskId" : null,
+ "region" : "xx-test-1"
+}`
+
+func main() {
+ flag.StringVar(&port, "p", "8081", "Port to listen to")
+ flag.Parse()
+
+ certificateGenerate()
+
+ l, err := net.Listen("tcp", ":"+port)
+ if err != nil {
+ log.Fatal(err)
+ }
+ // defer l.Close()
+ http.HandleFunc("/", rootHandler)
+
+ http.HandleFunc("/latest/dynamic/instance-identity/pkcs7", pkcsHandler)
+ http.HandleFunc("/latest/dynamic/instance-identity/document", documentHandler)
+ http.HandleFunc("/certificate", certificateHandler)
+
+ http.HandleFunc("/quit", quitHandler(l))
+
+ http.Serve(l, nil)
+}
+
+func certificateGenerate() {
+ var err error
+ priv, err = rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ log.Fatalf("failed to generate private key: %s", err)
+ }
+
+ 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: %s", err)
+ }
+
+ template := x509.Certificate{
+ SerialNumber: serialNumber,
+ Subject: pkix.Name{
+ Organization: []string{"Test"},
+ },
+ }
+
+ derBytes, err = x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
+ if err != nil {
+ log.Fatalf("Failed to create certificate: %s", err)
+ }
+}
+
+func rootHandler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/plain")
+ w.Write([]byte(""))
+}
+
+func pkcsHandler(w http.ResponseWriter, r *http.Request) {
+ cert, err := x509.ParseCertificate(derBytes)
+ if err != nil {
+ log.Fatalf("Cannot decode certificate: %s", err)
+ }
+
+ // Initialize a SignedData struct with content to be signed
+ signedData, err := pkcs7.NewSignedData([]byte(instanceDocument))
+ if err != nil {
+ log.Fatalf("Cannot initialize signed data: %s", err)
+ }
+
+ // Add the signing cert and private key
+ if err := signedData.AddSigner(cert, priv, pkcs7.SignerInfoConfig{}); err != nil {
+ log.Fatalf("Cannot add signer: %s", err)
+ }
+
+ // Finish() to obtain the signature bytes
+ detachedSignature, err := signedData.Finish()
+ if err != nil {
+ log.Fatalf("Cannot finish signing data: %s", err)
+ }
+
+ encoded := pem.EncodeToMemory(&pem.Block{Type: "PKCS7", Bytes: detachedSignature})
+
+ encoded = bytes.TrimPrefix(encoded, []byte("-----BEGIN PKCS7-----\n"))
+ encoded = bytes.TrimSuffix(encoded, []byte("\n-----END PKCS7-----\n"))
+
+ w.Header().Set("Content-Type", "text/plain")
+ w.Write(encoded)
+}
+
+func documentHandler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ w.Write([]byte(instanceDocument))
+}
+
+func certificateHandler(w http.ResponseWriter, r *http.Request) {
+ encoded := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
+
+ w.Header().Set("Content-Type", "text/plain")
+ w.Write(encoded)
+}
+
+func quitHandler(l net.Listener) func(http.ResponseWriter, *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ l.Close()
+ w.WriteHeader(http.StatusNoContent)
+ }
+}
diff --git a/vault/auth.go b/vault/auth.go
index a4781139..960e61ef 100644
--- a/vault/auth.go
+++ b/vault/auth.go
@@ -5,9 +5,13 @@ import (
"io/ioutil"
"os"
"path"
+ "strings"
+ "time"
"github.com/blang/vfs"
+ "github.com/hairyhenderson/gomplate/aws"
"github.com/hairyhenderson/gomplate/env"
+ "github.com/hairyhenderson/gomplate/typeconv"
)
// GetToken -
@@ -27,6 +31,9 @@ func (v *Vault) GetToken() string {
if token := v.TokenLogin(); token != "" {
return token
}
+ if token := v.EC2Login(); token != "" {
+ return token
+ }
logFatal("All vault auth failed")
return ""
}
@@ -148,6 +155,41 @@ func (v *Vault) UserPassLogin() string {
return secret.Auth.ClientToken
}
+// EC2Login - AWS EC2 auth backend
+func (v *Vault) EC2Login() string {
+ role := env.Getenv("VAULT_AUTH_AWS_ROLE")
+ mount := env.Getenv("VAULT_AUTH_AWS_MOUNT", "aws")
+
+ vars := map[string]interface{}{}
+
+ if role != "" {
+ vars["role"] = role
+ }
+
+ opts := aws.ClientOptions{
+ Timeout: time.Duration(typeconv.MustAtoi(os.Getenv("AWS_TIMEOUT"))) * time.Millisecond,
+ }
+
+ meta := aws.NewEc2Meta(opts)
+
+ vars["pkcs7"] = strings.Replace(strings.TrimSpace(meta.Dynamic("instance-identity/pkcs7")), "\n", "", -1)
+
+ if vars["pkcs7"] == "" {
+ return ""
+ }
+
+ path := fmt.Sprintf("auth/%s/login", mount)
+ secret, err := v.client.Logical().Write(path, vars)
+ if err != nil {
+ logFatal("AWS EC2 logon failed", err)
+ }
+ if secret == nil {
+ logFatal("Empty response from AWS EC2 logon")
+ }
+
+ return secret.Auth.ClientToken
+}
+
// TokenLogin -
func (v *Vault) TokenLogin() string {
if token := env.Getenv("VAULT_TOKEN"); token != "" {
diff --git a/vendor/github.com/fullsailor/pkcs7/.gitignore b/vendor/github.com/fullsailor/pkcs7/.gitignore
new file mode 100644
index 00000000..daf913b1
--- /dev/null
+++ b/vendor/github.com/fullsailor/pkcs7/.gitignore
@@ -0,0 +1,24 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
diff --git a/vendor/github.com/fullsailor/pkcs7/.travis.yml b/vendor/github.com/fullsailor/pkcs7/.travis.yml
new file mode 100644
index 00000000..c7154e46
--- /dev/null
+++ b/vendor/github.com/fullsailor/pkcs7/.travis.yml
@@ -0,0 +1,6 @@
+language: go
+
+go:
+ - 1.6
+ - 1.7
+ - tip
diff --git a/vendor/github.com/fullsailor/pkcs7/LICENSE b/vendor/github.com/fullsailor/pkcs7/LICENSE
new file mode 100644
index 00000000..75f32090
--- /dev/null
+++ b/vendor/github.com/fullsailor/pkcs7/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Andrew Smith
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/vendor/github.com/fullsailor/pkcs7/README.md b/vendor/github.com/fullsailor/pkcs7/README.md
new file mode 100644
index 00000000..bfd948f3
--- /dev/null
+++ b/vendor/github.com/fullsailor/pkcs7/README.md
@@ -0,0 +1,8 @@
+# pkcs7
+
+[![GoDoc](https://godoc.org/github.com/fullsailor/pkcs7?status.svg)](https://godoc.org/github.com/fullsailor/pkcs7)
+[![Build Status](https://travis-ci.org/fullsailor/pkcs7.svg?branch=master)](https://travis-ci.org/fullsailor/pkcs7)
+
+pkcs7 implements parsing and creating signed and enveloped messages.
+
+- Documentation on [GoDoc](http://godoc.org/github.com/fullsailor/pkcs7)
diff --git a/vendor/github.com/fullsailor/pkcs7/ber.go b/vendor/github.com/fullsailor/pkcs7/ber.go
new file mode 100644
index 00000000..bf3e8042
--- /dev/null
+++ b/vendor/github.com/fullsailor/pkcs7/ber.go
@@ -0,0 +1,248 @@
+package pkcs7
+
+import (
+ "bytes"
+ "errors"
+)
+
+var encodeIndent = 0
+
+type asn1Object interface {
+ EncodeTo(writer *bytes.Buffer) error
+}
+
+type asn1Structured struct {
+ tagBytes []byte
+ content []asn1Object
+}
+
+func (s asn1Structured) EncodeTo(out *bytes.Buffer) error {
+ //fmt.Printf("%s--> tag: % X\n", strings.Repeat("| ", encodeIndent), s.tagBytes)
+ encodeIndent++
+ inner := new(bytes.Buffer)
+ for _, obj := range s.content {
+ err := obj.EncodeTo(inner)
+ if err != nil {
+ return err
+ }
+ }
+ encodeIndent--
+ out.Write(s.tagBytes)
+ encodeLength(out, inner.Len())
+ out.Write(inner.Bytes())
+ return nil
+}
+
+type asn1Primitive struct {
+ tagBytes []byte
+ length int
+ content []byte
+}
+
+func (p asn1Primitive) EncodeTo(out *bytes.Buffer) error {
+ _, err := out.Write(p.tagBytes)
+ if err != nil {
+ return err
+ }
+ if err = encodeLength(out, p.length); err != nil {
+ return err
+ }
+ //fmt.Printf("%s--> tag: % X length: %d\n", strings.Repeat("| ", encodeIndent), p.tagBytes, p.length)
+ //fmt.Printf("%s--> content length: %d\n", strings.Repeat("| ", encodeIndent), len(p.content))
+ out.Write(p.content)
+
+ return nil
+}
+
+func ber2der(ber []byte) ([]byte, error) {
+ if len(ber) == 0 {
+ return nil, errors.New("ber2der: input ber is empty")
+ }
+ //fmt.Printf("--> ber2der: Transcoding %d bytes\n", len(ber))
+ out := new(bytes.Buffer)
+
+ obj, _, err := readObject(ber, 0)
+ if err != nil {
+ return nil, err
+ }
+ obj.EncodeTo(out)
+
+ // if offset < len(ber) {
+ // return nil, fmt.Errorf("ber2der: Content longer than expected. Got %d, expected %d", offset, len(ber))
+ //}
+
+ return out.Bytes(), nil
+}
+
+// encodes lengths that are longer than 127 into string of bytes
+func marshalLongLength(out *bytes.Buffer, i int) (err error) {
+ n := lengthLength(i)
+
+ for ; n > 0; n-- {
+ err = out.WriteByte(byte(i >> uint((n-1)*8)))
+ if err != nil {
+ return
+ }
+ }
+
+ return nil
+}
+
+// computes the byte length of an encoded length value
+func lengthLength(i int) (numBytes int) {
+ numBytes = 1
+ for i > 255 {
+ numBytes++
+ i >>= 8
+ }
+ return
+}
+
+// encodes the length in DER format
+// If the length fits in 7 bits, the value is encoded directly.
+//
+// Otherwise, the number of bytes to encode the length is first determined.
+// This number is likely to be 4 or less for a 32bit length. This number is
+// added to 0x80. The length is encoded in big endian encoding follow after
+//
+// Examples:
+// length | byte 1 | bytes n
+// 0 | 0x00 | -
+// 120 | 0x78 | -
+// 200 | 0x81 | 0xC8
+// 500 | 0x82 | 0x01 0xF4
+//
+func encodeLength(out *bytes.Buffer, length int) (err error) {
+ if length >= 128 {
+ l := lengthLength(length)
+ err = out.WriteByte(0x80 | byte(l))
+ if err != nil {
+ return
+ }
+ err = marshalLongLength(out, length)
+ if err != nil {
+ return
+ }
+ } else {
+ err = out.WriteByte(byte(length))
+ if err != nil {
+ return
+ }
+ }
+ return
+}
+
+func readObject(ber []byte, offset int) (asn1Object, int, error) {
+ //fmt.Printf("\n====> Starting readObject at offset: %d\n\n", offset)
+ tagStart := offset
+ b := ber[offset]
+ offset++
+ tag := b & 0x1F // last 5 bits
+ if tag == 0x1F {
+ tag = 0
+ for ber[offset] >= 0x80 {
+ tag = tag*128 + ber[offset] - 0x80
+ offset++
+ }
+ tag = tag*128 + ber[offset] - 0x80
+ offset++
+ }
+ tagEnd := offset
+
+ kind := b & 0x20
+ /*
+ if kind == 0 {
+ fmt.Print("--> Primitive\n")
+ } else {
+ fmt.Print("--> Constructed\n")
+ }
+ */
+ // read length
+ var length int
+ l := ber[offset]
+ offset++
+ indefinite := false
+ if l > 0x80 {
+ numberOfBytes := (int)(l & 0x7F)
+ if numberOfBytes > 4 { // int is only guaranteed to be 32bit
+ return nil, 0, errors.New("ber2der: BER tag length too long")
+ }
+ if numberOfBytes == 4 && (int)(ber[offset]) > 0x7F {
+ return nil, 0, errors.New("ber2der: BER tag length is negative")
+ }
+ if 0x0 == (int)(ber[offset]) {
+ return nil, 0, errors.New("ber2der: BER tag length has leading zero")
+ }
+ //fmt.Printf("--> (compute length) indicator byte: %x\n", l)
+ //fmt.Printf("--> (compute length) length bytes: % X\n", ber[offset:offset+numberOfBytes])
+ for i := 0; i < numberOfBytes; i++ {
+ length = length*256 + (int)(ber[offset])
+ offset++
+ }
+ } else if l == 0x80 {
+ indefinite = true
+ } else {
+ length = (int)(l)
+ }
+
+ //fmt.Printf("--> length : %d\n", length)
+ contentEnd := offset + length
+ if contentEnd > len(ber) {
+ return nil, 0, errors.New("ber2der: BER tag length is more than available data")
+ }
+ //fmt.Printf("--> content start : %d\n", offset)
+ //fmt.Printf("--> content end : %d\n", contentEnd)
+ //fmt.Printf("--> content : % X\n", ber[offset:contentEnd])
+ var obj asn1Object
+ if indefinite && kind == 0 {
+ return nil, 0, errors.New("ber2der: Indefinite form tag must have constructed encoding")
+ }
+ if kind == 0 {
+ obj = asn1Primitive{
+ tagBytes: ber[tagStart:tagEnd],
+ length: length,
+ content: ber[offset:contentEnd],
+ }
+ } else {
+ var subObjects []asn1Object
+ for (offset < contentEnd) || indefinite {
+ var subObj asn1Object
+ var err error
+ subObj, offset, err = readObject(ber, offset)
+ if err != nil {
+ return nil, 0, err
+ }
+ subObjects = append(subObjects, subObj)
+
+ if indefinite {
+ terminated, err := isIndefiniteTermination(ber, offset)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ if terminated {
+ break
+ }
+ }
+ }
+ obj = asn1Structured{
+ tagBytes: ber[tagStart:tagEnd],
+ content: subObjects,
+ }
+ }
+
+ // Apply indefinite form length with 0x0000 terminator.
+ if indefinite {
+ contentEnd = offset + 2
+ }
+
+ return obj, contentEnd, nil
+}
+
+func isIndefiniteTermination(ber []byte, offset int) (bool, error) {
+ if len(ber) - offset < 2 {
+ return false, errors.New("ber2der: Invalid BER format")
+ }
+
+ return bytes.Index(ber[offset:], []byte{0x0, 0x0}) == 0, nil
+}
diff --git a/vendor/github.com/fullsailor/pkcs7/ber_test.go b/vendor/github.com/fullsailor/pkcs7/ber_test.go
new file mode 100644
index 00000000..19a0f514
--- /dev/null
+++ b/vendor/github.com/fullsailor/pkcs7/ber_test.go
@@ -0,0 +1,97 @@
+package pkcs7
+
+import (
+ "bytes"
+ "encoding/asn1"
+ "strings"
+ "testing"
+)
+
+func TestBer2Der(t *testing.T) {
+ // indefinite length fixture
+ ber := []byte{0x30, 0x80, 0x02, 0x01, 0x01, 0x00, 0x00}
+ expected := []byte{0x30, 0x03, 0x02, 0x01, 0x01}
+ der, err := ber2der(ber)
+ if err != nil {
+ t.Fatalf("ber2der failed with error: %v", err)
+ }
+ if bytes.Compare(der, expected) != 0 {
+ t.Errorf("ber2der result did not match.\n\tExpected: % X\n\tActual: % X", expected, der)
+ }
+
+ if der2, err := ber2der(der); err != nil {
+ t.Errorf("ber2der on DER bytes failed with error: %v", err)
+ } else {
+ if !bytes.Equal(der, der2) {
+ t.Error("ber2der is not idempotent")
+ }
+ }
+ var thing struct {
+ Number int
+ }
+ rest, err := asn1.Unmarshal(der, &thing)
+ if err != nil {
+ t.Errorf("Cannot parse resulting DER because: %v", err)
+ } else if len(rest) > 0 {
+ t.Errorf("Resulting DER has trailing data: % X", rest)
+ }
+}
+
+func TestBer2Der_Negatives(t *testing.T) {
+ fixtures := []struct {
+ Input []byte
+ ErrorContains string
+ }{
+ {[]byte{0x30, 0x85}, "length too long"},
+ {[]byte{0x30, 0x84, 0x80, 0x0, 0x0, 0x0}, "length is negative"},
+ {[]byte{0x30, 0x82, 0x0, 0x1}, "length has leading zero"},
+ {[]byte{0x30, 0x80, 0x1, 0x2, 0x1, 0x2}, "Invalid BER format"},
+ {[]byte{0x30, 0x03, 0x01, 0x02}, "length is more than available data"},
+ }
+
+ for _, fixture := range fixtures {
+ _, err := ber2der(fixture.Input)
+ if err == nil {
+ t.Errorf("No error thrown. Expected: %s", fixture.ErrorContains)
+ }
+ if !strings.Contains(err.Error(), fixture.ErrorContains) {
+ t.Errorf("Unexpected error thrown.\n\tExpected: /%s/\n\tActual: %s", fixture.ErrorContains, err.Error())
+ }
+ }
+}
+
+func TestBer2Der_NestedMultipleIndefinite(t *testing.T) {
+ // indefinite length fixture
+ ber := []byte{0x30, 0x80, 0x30, 0x80, 0x02, 0x01, 0x01, 0x00, 0x00, 0x30, 0x80, 0x02, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00}
+ expected := []byte{0x30, 0x0A, 0x30, 0x03, 0x02, 0x01, 0x01, 0x30, 0x03, 0x02, 0x01, 0x02}
+
+ der, err := ber2der(ber)
+ if err != nil {
+ t.Fatalf("ber2der failed with error: %v", err)
+ }
+ if bytes.Compare(der, expected) != 0 {
+ t.Errorf("ber2der result did not match.\n\tExpected: % X\n\tActual: % X", expected, der)
+ }
+
+ if der2, err := ber2der(der); err != nil {
+ t.Errorf("ber2der on DER bytes failed with error: %v", err)
+ } else {
+ if !bytes.Equal(der, der2) {
+ t.Error("ber2der is not idempotent")
+ }
+ }
+ var thing struct {
+ Nest1 struct {
+ Number int
+ }
+ Nest2 struct {
+ Number int
+ }
+ }
+ rest, err := asn1.Unmarshal(der, &thing)
+ if err != nil {
+ t.Errorf("Cannot parse resulting DER because: %v", err)
+ } else if len(rest) > 0 {
+ t.Errorf("Resulting DER has trailing data: % X", rest)
+ }
+}
diff --git a/vendor/github.com/fullsailor/pkcs7/pkcs7.go b/vendor/github.com/fullsailor/pkcs7/pkcs7.go
new file mode 100644
index 00000000..8d5af853
--- /dev/null
+++ b/vendor/github.com/fullsailor/pkcs7/pkcs7.go
@@ -0,0 +1,940 @@
+// Package pkcs7 implements parsing and generation of some PKCS#7 structures.
+package pkcs7
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/des"
+ "crypto/hmac"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "errors"
+ "fmt"
+ "math/big"
+ "sort"
+ "time"
+
+ _ "crypto/sha1" // for crypto.SHA1
+)
+
+// PKCS7 Represents a PKCS7 structure
+type PKCS7 struct {
+ Content []byte
+ Certificates []*x509.Certificate
+ CRLs []pkix.CertificateList
+ Signers []signerInfo
+ raw interface{}
+}
+
+type contentInfo struct {
+ ContentType asn1.ObjectIdentifier
+ Content asn1.RawValue `asn1:"explicit,optional,tag:0"`
+}
+
+// ErrUnsupportedContentType is returned when a PKCS7 content is not supported.
+// Currently only Data (1.2.840.113549.1.7.1), Signed Data (1.2.840.113549.1.7.2),
+// and Enveloped Data are supported (1.2.840.113549.1.7.3)
+var ErrUnsupportedContentType = errors.New("pkcs7: cannot parse data: unimplemented content type")
+
+type unsignedData []byte
+
+var (
+ oidData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1}
+ oidSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2}
+ oidEnvelopedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 3}
+ oidSignedAndEnvelopedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 4}
+ oidDigestedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 5}
+ oidEncryptedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 6}
+ oidAttributeContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3}
+ oidAttributeMessageDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4}
+ oidAttributeSigningTime = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 5}
+)
+
+type signedData struct {
+ Version int `asn1:"default:1"`
+ DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"`
+ ContentInfo contentInfo
+ Certificates rawCertificates `asn1:"optional,tag:0"`
+ CRLs []pkix.CertificateList `asn1:"optional,tag:1"`
+ SignerInfos []signerInfo `asn1:"set"`
+}
+
+type rawCertificates struct {
+ Raw asn1.RawContent
+}
+
+type envelopedData struct {
+ Version int
+ RecipientInfos []recipientInfo `asn1:"set"`
+ EncryptedContentInfo encryptedContentInfo
+}
+
+type recipientInfo struct {
+ Version int
+ IssuerAndSerialNumber issuerAndSerial
+ KeyEncryptionAlgorithm pkix.AlgorithmIdentifier
+ EncryptedKey []byte
+}
+
+type encryptedContentInfo struct {
+ ContentType asn1.ObjectIdentifier
+ ContentEncryptionAlgorithm pkix.AlgorithmIdentifier
+ EncryptedContent asn1.RawValue `asn1:"tag:0,optional,explicit"`
+}
+
+type attribute struct {
+ Type asn1.ObjectIdentifier
+ Value asn1.RawValue `asn1:"set"`
+}
+
+type issuerAndSerial struct {
+ IssuerName asn1.RawValue
+ SerialNumber *big.Int
+}
+
+// MessageDigestMismatchError is returned when the signer data digest does not
+// match the computed digest for the contained content
+type MessageDigestMismatchError struct {
+ ExpectedDigest []byte
+ ActualDigest []byte
+}
+
+func (err *MessageDigestMismatchError) Error() string {
+ return fmt.Sprintf("pkcs7: Message digest mismatch\n\tExpected: %X\n\tActual : %X", err.ExpectedDigest, err.ActualDigest)
+}
+
+type signerInfo struct {
+ Version int `asn1:"default:1"`
+ IssuerAndSerialNumber issuerAndSerial
+ DigestAlgorithm pkix.AlgorithmIdentifier
+ AuthenticatedAttributes []attribute `asn1:"optional,tag:0"`
+ DigestEncryptionAlgorithm pkix.AlgorithmIdentifier
+ EncryptedDigest []byte
+ UnauthenticatedAttributes []attribute `asn1:"optional,tag:1"`
+}
+
+// Parse decodes a DER encoded PKCS7 package
+func Parse(data []byte) (p7 *PKCS7, err error) {
+ if len(data) == 0 {
+ return nil, errors.New("pkcs7: input data is empty")
+ }
+ var info contentInfo
+ der, err := ber2der(data)
+ if err != nil {
+ return nil, err
+ }
+ rest, err := asn1.Unmarshal(der, &info)
+ if len(rest) > 0 {
+ err = asn1.SyntaxError{Msg: "trailing data"}
+ return
+ }
+ if err != nil {
+ return
+ }
+
+ // fmt.Printf("--> Content Type: %s", info.ContentType)
+ switch {
+ case info.ContentType.Equal(oidSignedData):
+ return parseSignedData(info.Content.Bytes)
+ case info.ContentType.Equal(oidEnvelopedData):
+ return parseEnvelopedData(info.Content.Bytes)
+ }
+ return nil, ErrUnsupportedContentType
+}
+
+func parseSignedData(data []byte) (*PKCS7, error) {
+ var sd signedData
+ asn1.Unmarshal(data, &sd)
+ certs, err := sd.Certificates.Parse()
+ if err != nil {
+ return nil, err
+ }
+ // fmt.Printf("--> Signed Data Version %d\n", sd.Version)
+
+ var compound asn1.RawValue
+ var content unsignedData
+
+ // The Content.Bytes maybe empty on PKI responses.
+ if len(sd.ContentInfo.Content.Bytes) > 0 {
+ if _, err := asn1.Unmarshal(sd.ContentInfo.Content.Bytes, &compound); err != nil {
+ return nil, err
+ }
+ }
+ // Compound octet string
+ if compound.IsCompound {
+ if _, err = asn1.Unmarshal(compound.Bytes, &content); err != nil {
+ return nil, err
+ }
+ } else {
+ // assuming this is tag 04
+ content = compound.Bytes
+ }
+ return &PKCS7{
+ Content: content,
+ Certificates: certs,
+ CRLs: sd.CRLs,
+ Signers: sd.SignerInfos,
+ raw: sd}, nil
+}
+
+func (raw rawCertificates) Parse() ([]*x509.Certificate, error) {
+ if len(raw.Raw) == 0 {
+ return nil, nil
+ }
+
+ var val asn1.RawValue
+ if _, err := asn1.Unmarshal(raw.Raw, &val); err != nil {
+ return nil, err
+ }
+
+ return x509.ParseCertificates(val.Bytes)
+}
+
+func parseEnvelopedData(data []byte) (*PKCS7, error) {
+ var ed envelopedData
+ if _, err := asn1.Unmarshal(data, &ed); err != nil {
+ return nil, err
+ }
+ return &PKCS7{
+ raw: ed,
+ }, nil
+}
+
+// Verify checks the signatures of a PKCS7 object
+// WARNING: Verify does not check signing time or verify certificate chains at
+// this time.
+func (p7 *PKCS7) Verify() (err error) {
+ if len(p7.Signers) == 0 {
+ return errors.New("pkcs7: Message has no signers")
+ }
+ for _, signer := range p7.Signers {
+ if err := verifySignature(p7, signer); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func verifySignature(p7 *PKCS7, signer signerInfo) error {
+ signedData := p7.Content
+ if len(signer.AuthenticatedAttributes) > 0 {
+ // TODO(fullsailor): First check the content type match
+ var digest []byte
+ err := unmarshalAttribute(signer.AuthenticatedAttributes, oidAttributeMessageDigest, &digest)
+ if err != nil {
+ return err
+ }
+ hash, err := getHashForOID(signer.DigestAlgorithm.Algorithm)
+ if err != nil {
+ return err
+ }
+ h := hash.New()
+ h.Write(p7.Content)
+ computed := h.Sum(nil)
+ if !hmac.Equal(digest, computed) {
+ return &MessageDigestMismatchError{
+ ExpectedDigest: digest,
+ ActualDigest: computed,
+ }
+ }
+ // TODO(fullsailor): Optionally verify certificate chain
+ // TODO(fullsailor): Optionally verify signingTime against certificate NotAfter/NotBefore
+ signedData, err = marshalAttributes(signer.AuthenticatedAttributes)
+ if err != nil {
+ return err
+ }
+ }
+ cert := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber)
+ if cert == nil {
+ return errors.New("pkcs7: No certificate for signer")
+ }
+
+ algo := x509.SHA1WithRSA
+ return cert.CheckSignature(algo, signedData, signer.EncryptedDigest)
+}
+
+func marshalAttributes(attrs []attribute) ([]byte, error) {
+ encodedAttributes, err := asn1.Marshal(struct {
+ A []attribute `asn1:"set"`
+ }{A: attrs})
+ if err != nil {
+ return nil, err
+ }
+
+ // Remove the leading sequence octets
+ var raw asn1.RawValue
+ asn1.Unmarshal(encodedAttributes, &raw)
+ return raw.Bytes, nil
+}
+
+var (
+ oidDigestAlgorithmSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26}
+ oidEncryptionAlgorithmRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}
+)
+
+func getCertFromCertsByIssuerAndSerial(certs []*x509.Certificate, ias issuerAndSerial) *x509.Certificate {
+ for _, cert := range certs {
+ if isCertMatchForIssuerAndSerial(cert, ias) {
+ return cert
+ }
+ }
+ return nil
+}
+
+func getHashForOID(oid asn1.ObjectIdentifier) (crypto.Hash, error) {
+ switch {
+ case oid.Equal(oidDigestAlgorithmSHA1):
+ return crypto.SHA1, nil
+ }
+ return crypto.Hash(0), ErrUnsupportedAlgorithm
+}
+
+// GetOnlySigner returns an x509.Certificate for the first signer of the signed
+// data payload. If there are more or less than one signer, nil is returned
+func (p7 *PKCS7) GetOnlySigner() *x509.Certificate {
+ if len(p7.Signers) != 1 {
+ return nil
+ }
+ signer := p7.Signers[0]
+ return getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber)
+}
+
+// ErrUnsupportedAlgorithm tells you when our quick dev assumptions have failed
+var ErrUnsupportedAlgorithm = errors.New("pkcs7: cannot decrypt data: only RSA, DES, DES-EDE3, AES-256-CBC and AES-128-GCM supported")
+
+// ErrNotEncryptedContent is returned when attempting to Decrypt data that is not encrypted data
+var ErrNotEncryptedContent = errors.New("pkcs7: content data is a decryptable data type")
+
+// Decrypt decrypts encrypted content info for recipient cert and private key
+func (p7 *PKCS7) Decrypt(cert *x509.Certificate, pk crypto.PrivateKey) ([]byte, error) {
+ data, ok := p7.raw.(envelopedData)
+ if !ok {
+ return nil, ErrNotEncryptedContent
+ }
+ recipient := selectRecipientForCertificate(data.RecipientInfos, cert)
+ if recipient.EncryptedKey == nil {
+ return nil, errors.New("pkcs7: no enveloped recipient for provided certificate")
+ }
+ if priv := pk.(*rsa.PrivateKey); priv != nil {
+ var contentKey []byte
+ contentKey, err := rsa.DecryptPKCS1v15(rand.Reader, priv, recipient.EncryptedKey)
+ if err != nil {
+ return nil, err
+ }
+ return data.EncryptedContentInfo.decrypt(contentKey)
+ }
+ fmt.Printf("Unsupported Private Key: %v\n", pk)
+ return nil, ErrUnsupportedAlgorithm
+}
+
+var oidEncryptionAlgorithmDESCBC = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 7}
+var oidEncryptionAlgorithmDESEDE3CBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 3, 7}
+var oidEncryptionAlgorithmAES256CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 42}
+var oidEncryptionAlgorithmAES128GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 6}
+var oidEncryptionAlgorithmAES128CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 2}
+
+func (eci encryptedContentInfo) decrypt(key []byte) ([]byte, error) {
+ alg := eci.ContentEncryptionAlgorithm.Algorithm
+ if !alg.Equal(oidEncryptionAlgorithmDESCBC) &&
+ !alg.Equal(oidEncryptionAlgorithmDESEDE3CBC) &&
+ !alg.Equal(oidEncryptionAlgorithmAES256CBC) &&
+ !alg.Equal(oidEncryptionAlgorithmAES128CBC) &&
+ !alg.Equal(oidEncryptionAlgorithmAES128GCM) {
+ fmt.Printf("Unsupported Content Encryption Algorithm: %s\n", alg)
+ return nil, ErrUnsupportedAlgorithm
+ }
+
+ // EncryptedContent can either be constructed of multple OCTET STRINGs
+ // or _be_ a tagged OCTET STRING
+ var cyphertext []byte
+ if eci.EncryptedContent.IsCompound {
+ // Complex case to concat all of the children OCTET STRINGs
+ var buf bytes.Buffer
+ cypherbytes := eci.EncryptedContent.Bytes
+ for {
+ var part []byte
+ cypherbytes, _ = asn1.Unmarshal(cypherbytes, &part)
+ buf.Write(part)
+ if cypherbytes == nil {
+ break
+ }
+ }
+ cyphertext = buf.Bytes()
+ } else {
+ // Simple case, the bytes _are_ the cyphertext
+ cyphertext = eci.EncryptedContent.Bytes
+ }
+
+ var block cipher.Block
+ var err error
+
+ switch {
+ case alg.Equal(oidEncryptionAlgorithmDESCBC):
+ block, err = des.NewCipher(key)
+ case alg.Equal(oidEncryptionAlgorithmDESEDE3CBC):
+ block, err = des.NewTripleDESCipher(key)
+ case alg.Equal(oidEncryptionAlgorithmAES256CBC):
+ fallthrough
+ case alg.Equal(oidEncryptionAlgorithmAES128GCM), alg.Equal(oidEncryptionAlgorithmAES128CBC):
+ block, err = aes.NewCipher(key)
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ if alg.Equal(oidEncryptionAlgorithmAES128GCM) {
+ params := aesGCMParameters{}
+ paramBytes := eci.ContentEncryptionAlgorithm.Parameters.Bytes
+
+ _, err := asn1.Unmarshal(paramBytes, &params)
+ if err != nil {
+ return nil, err
+ }
+
+ gcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(params.Nonce) != gcm.NonceSize() {
+ return nil, errors.New("pkcs7: encryption algorithm parameters are incorrect")
+ }
+ if params.ICVLen != gcm.Overhead() {
+ return nil, errors.New("pkcs7: encryption algorithm parameters are incorrect")
+ }
+
+ plaintext, err := gcm.Open(nil, params.Nonce, cyphertext, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ return plaintext, nil
+ }
+
+ iv := eci.ContentEncryptionAlgorithm.Parameters.Bytes
+ if len(iv) != block.BlockSize() {
+ return nil, errors.New("pkcs7: encryption algorithm parameters are malformed")
+ }
+ mode := cipher.NewCBCDecrypter(block, iv)
+ plaintext := make([]byte, len(cyphertext))
+ mode.CryptBlocks(plaintext, cyphertext)
+ if plaintext, err = unpad(plaintext, mode.BlockSize()); err != nil {
+ return nil, err
+ }
+ return plaintext, nil
+}
+
+func selectRecipientForCertificate(recipients []recipientInfo, cert *x509.Certificate) recipientInfo {
+ for _, recp := range recipients {
+ if isCertMatchForIssuerAndSerial(cert, recp.IssuerAndSerialNumber) {
+ return recp
+ }
+ }
+ return recipientInfo{}
+}
+
+func isCertMatchForIssuerAndSerial(cert *x509.Certificate, ias issuerAndSerial) bool {
+ return cert.SerialNumber.Cmp(ias.SerialNumber) == 0 && bytes.Compare(cert.RawIssuer, ias.IssuerName.FullBytes) == 0
+}
+
+func pad(data []byte, blocklen int) ([]byte, error) {
+ if blocklen < 1 {
+ return nil, fmt.Errorf("invalid blocklen %d", blocklen)
+ }
+ padlen := blocklen - (len(data) % blocklen)
+ if padlen == 0 {
+ padlen = blocklen
+ }
+ pad := bytes.Repeat([]byte{byte(padlen)}, padlen)
+ return append(data, pad...), nil
+}
+
+func unpad(data []byte, blocklen int) ([]byte, error) {
+ if blocklen < 1 {
+ return nil, fmt.Errorf("invalid blocklen %d", blocklen)
+ }
+ if len(data)%blocklen != 0 || len(data) == 0 {
+ return nil, fmt.Errorf("invalid data len %d", len(data))
+ }
+
+ // the last byte is the length of padding
+ padlen := int(data[len(data)-1])
+
+ // check padding integrity, all bytes should be the same
+ pad := data[len(data)-padlen:]
+ for _, padbyte := range pad {
+ if padbyte != byte(padlen) {
+ return nil, errors.New("invalid padding")
+ }
+ }
+
+ return data[:len(data)-padlen], nil
+}
+
+func unmarshalAttribute(attrs []attribute, attributeType asn1.ObjectIdentifier, out interface{}) error {
+ for _, attr := range attrs {
+ if attr.Type.Equal(attributeType) {
+ _, err := asn1.Unmarshal(attr.Value.Bytes, out)
+ return err
+ }
+ }
+ return errors.New("pkcs7: attribute type not in attributes")
+}
+
+// UnmarshalSignedAttribute decodes a single attribute from the signer info
+func (p7 *PKCS7) UnmarshalSignedAttribute(attributeType asn1.ObjectIdentifier, out interface{}) error {
+ sd, ok := p7.raw.(signedData)
+ if !ok {
+ return errors.New("pkcs7: payload is not signedData content")
+ }
+ if len(sd.SignerInfos) < 1 {
+ return errors.New("pkcs7: payload has no signers")
+ }
+ attributes := sd.SignerInfos[0].AuthenticatedAttributes
+ return unmarshalAttribute(attributes, attributeType, out)
+}
+
+// SignedData is an opaque data structure for creating signed data payloads
+type SignedData struct {
+ sd signedData
+ certs []*x509.Certificate
+ messageDigest []byte
+}
+
+// Attribute represents a key value pair attribute. Value must be marshalable byte
+// `encoding/asn1`
+type Attribute struct {
+ Type asn1.ObjectIdentifier
+ Value interface{}
+}
+
+// SignerInfoConfig are optional values to include when adding a signer
+type SignerInfoConfig struct {
+ ExtraSignedAttributes []Attribute
+}
+
+// NewSignedData initializes a SignedData with content
+func NewSignedData(data []byte) (*SignedData, error) {
+ content, err := asn1.Marshal(data)
+ if err != nil {
+ return nil, err
+ }
+ ci := contentInfo{
+ ContentType: oidData,
+ Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true},
+ }
+ digAlg := pkix.AlgorithmIdentifier{
+ Algorithm: oidDigestAlgorithmSHA1,
+ }
+ h := crypto.SHA1.New()
+ h.Write(data)
+ md := h.Sum(nil)
+ sd := signedData{
+ ContentInfo: ci,
+ Version: 1,
+ DigestAlgorithmIdentifiers: []pkix.AlgorithmIdentifier{digAlg},
+ }
+ return &SignedData{sd: sd, messageDigest: md}, nil
+}
+
+type attributes struct {
+ types []asn1.ObjectIdentifier
+ values []interface{}
+}
+
+// Add adds the attribute, maintaining insertion order
+func (attrs *attributes) Add(attrType asn1.ObjectIdentifier, value interface{}) {
+ attrs.types = append(attrs.types, attrType)
+ attrs.values = append(attrs.values, value)
+}
+
+type sortableAttribute struct {
+ SortKey []byte
+ Attribute attribute
+}
+
+type attributeSet []sortableAttribute
+
+func (sa attributeSet) Len() int {
+ return len(sa)
+}
+
+func (sa attributeSet) Less(i, j int) bool {
+ return bytes.Compare(sa[i].SortKey, sa[j].SortKey) < 0
+}
+
+func (sa attributeSet) Swap(i, j int) {
+ sa[i], sa[j] = sa[j], sa[i]
+}
+
+func (sa attributeSet) Attributes() []attribute {
+ attrs := make([]attribute, len(sa))
+ for i, attr := range sa {
+ attrs[i] = attr.Attribute
+ }
+ return attrs
+}
+
+func (attrs *attributes) ForMarshaling() ([]attribute, error) {
+ sortables := make(attributeSet, len(attrs.types))
+ for i := range sortables {
+ attrType := attrs.types[i]
+ attrValue := attrs.values[i]
+ asn1Value, err := asn1.Marshal(attrValue)
+ if err != nil {
+ return nil, err
+ }
+ attr := attribute{
+ Type: attrType,
+ Value: asn1.RawValue{Tag: 17, IsCompound: true, Bytes: asn1Value}, // 17 == SET tag
+ }
+ encoded, err := asn1.Marshal(attr)
+ if err != nil {
+ return nil, err
+ }
+ sortables[i] = sortableAttribute{
+ SortKey: encoded,
+ Attribute: attr,
+ }
+ }
+ sort.Sort(sortables)
+ return sortables.Attributes(), nil
+}
+
+// AddSigner signs attributes about the content and adds certificate to payload
+func (sd *SignedData) AddSigner(cert *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error {
+ attrs := &attributes{}
+ attrs.Add(oidAttributeContentType, sd.sd.ContentInfo.ContentType)
+ attrs.Add(oidAttributeMessageDigest, sd.messageDigest)
+ attrs.Add(oidAttributeSigningTime, time.Now())
+ for _, attr := range config.ExtraSignedAttributes {
+ attrs.Add(attr.Type, attr.Value)
+ }
+ finalAttrs, err := attrs.ForMarshaling()
+ if err != nil {
+ return err
+ }
+ signature, err := signAttributes(finalAttrs, pkey, crypto.SHA1)
+ if err != nil {
+ return err
+ }
+
+ ias, err := cert2issuerAndSerial(cert)
+ if err != nil {
+ return err
+ }
+
+ signer := signerInfo{
+ AuthenticatedAttributes: finalAttrs,
+ DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidDigestAlgorithmSHA1},
+ DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: oidEncryptionAlgorithmRSA},
+ IssuerAndSerialNumber: ias,
+ EncryptedDigest: signature,
+ Version: 1,
+ }
+ // create signature of signed attributes
+ sd.certs = append(sd.certs, cert)
+ sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer)
+ return nil
+}
+
+// AddCertificate adds the certificate to the payload. Useful for parent certificates
+func (sd *SignedData) AddCertificate(cert *x509.Certificate) {
+ sd.certs = append(sd.certs, cert)
+}
+
+// Detach removes content from the signed data struct to make it a detached signature.
+// This must be called right before Finish()
+func (sd *SignedData) Detach() {
+ sd.sd.ContentInfo = contentInfo{ContentType: oidSignedData}
+}
+
+// Finish marshals the content and its signers
+func (sd *SignedData) Finish() ([]byte, error) {
+ sd.sd.Certificates = marshalCertificates(sd.certs)
+ inner, err := asn1.Marshal(sd.sd)
+ if err != nil {
+ return nil, err
+ }
+ outer := contentInfo{
+ ContentType: oidSignedData,
+ Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: inner, IsCompound: true},
+ }
+ return asn1.Marshal(outer)
+}
+
+func cert2issuerAndSerial(cert *x509.Certificate) (issuerAndSerial, error) {
+ var ias issuerAndSerial
+ // The issuer RDNSequence has to match exactly the sequence in the certificate
+ // We cannot use cert.Issuer.ToRDNSequence() here since it mangles the sequence
+ ias.IssuerName = asn1.RawValue{FullBytes: cert.RawIssuer}
+ ias.SerialNumber = cert.SerialNumber
+
+ return ias, nil
+}
+
+// signs the DER encoded form of the attributes with the private key
+func signAttributes(attrs []attribute, pkey crypto.PrivateKey, hash crypto.Hash) ([]byte, error) {
+ attrBytes, err := marshalAttributes(attrs)
+ if err != nil {
+ return nil, err
+ }
+ h := hash.New()
+ h.Write(attrBytes)
+ hashed := h.Sum(nil)
+ switch priv := pkey.(type) {
+ case *rsa.PrivateKey:
+ return rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA1, hashed)
+ }
+ return nil, ErrUnsupportedAlgorithm
+}
+
+// concats and wraps the certificates in the RawValue structure
+func marshalCertificates(certs []*x509.Certificate) rawCertificates {
+ var buf bytes.Buffer
+ for _, cert := range certs {
+ buf.Write(cert.Raw)
+ }
+ rawCerts, _ := marshalCertificateBytes(buf.Bytes())
+ return rawCerts
+}
+
+// Even though, the tag & length are stripped out during marshalling the
+// RawContent, we have to encode it into the RawContent. If its missing,
+// then `asn1.Marshal()` will strip out the certificate wrapper instead.
+func marshalCertificateBytes(certs []byte) (rawCertificates, error) {
+ var val = asn1.RawValue{Bytes: certs, Class: 2, Tag: 0, IsCompound: true}
+ b, err := asn1.Marshal(val)
+ if err != nil {
+ return rawCertificates{}, err
+ }
+ return rawCertificates{Raw: b}, nil
+}
+
+// DegenerateCertificate creates a signed data structure containing only the
+// provided certificate or certificate chain.
+func DegenerateCertificate(cert []byte) ([]byte, error) {
+ rawCert, err := marshalCertificateBytes(cert)
+ if err != nil {
+ return nil, err
+ }
+ emptyContent := contentInfo{ContentType: oidData}
+ sd := signedData{
+ Version: 1,
+ ContentInfo: emptyContent,
+ Certificates: rawCert,
+ CRLs: []pkix.CertificateList{},
+ }
+ content, err := asn1.Marshal(sd)
+ if err != nil {
+ return nil, err
+ }
+ signedContent := contentInfo{
+ ContentType: oidSignedData,
+ Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true},
+ }
+ return asn1.Marshal(signedContent)
+}
+
+const (
+ EncryptionAlgorithmDESCBC = iota
+ EncryptionAlgorithmAES128GCM
+)
+
+// ContentEncryptionAlgorithm determines the algorithm used to encrypt the
+// plaintext message. Change the value of this variable to change which
+// algorithm is used in the Encrypt() function.
+var ContentEncryptionAlgorithm = EncryptionAlgorithmDESCBC
+
+// ErrUnsupportedEncryptionAlgorithm is returned when attempting to encrypt
+// content with an unsupported algorithm.
+var ErrUnsupportedEncryptionAlgorithm = errors.New("pkcs7: cannot encrypt content: only DES-CBC and AES-128-GCM supported")
+
+const nonceSize = 12
+
+type aesGCMParameters struct {
+ Nonce []byte `asn1:"tag:4"`
+ ICVLen int
+}
+
+func encryptAES128GCM(content []byte) ([]byte, *encryptedContentInfo, error) {
+ // Create AES key and nonce
+ key := make([]byte, 16)
+ nonce := make([]byte, nonceSize)
+
+ _, err := rand.Read(key)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ _, err = rand.Read(nonce)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Encrypt content
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ gcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ ciphertext := gcm.Seal(nil, nonce, content, nil)
+
+ // Prepare ASN.1 Encrypted Content Info
+ paramSeq := aesGCMParameters{
+ Nonce: nonce,
+ ICVLen: gcm.Overhead(),
+ }
+
+ paramBytes, err := asn1.Marshal(paramSeq)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ eci := encryptedContentInfo{
+ ContentType: oidData,
+ ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{
+ Algorithm: oidEncryptionAlgorithmAES128GCM,
+ Parameters: asn1.RawValue{
+ Tag: asn1.TagSequence,
+ Bytes: paramBytes,
+ },
+ },
+ EncryptedContent: marshalEncryptedContent(ciphertext),
+ }
+
+ return key, &eci, nil
+}
+
+func encryptDESCBC(content []byte) ([]byte, *encryptedContentInfo, error) {
+ // Create DES key & CBC IV
+ key := make([]byte, 8)
+ iv := make([]byte, des.BlockSize)
+ _, err := rand.Read(key)
+ if err != nil {
+ return nil, nil, err
+ }
+ _, err = rand.Read(iv)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Encrypt padded content
+ block, err := des.NewCipher(key)
+ if err != nil {
+ return nil, nil, err
+ }
+ mode := cipher.NewCBCEncrypter(block, iv)
+ plaintext, err := pad(content, mode.BlockSize())
+ cyphertext := make([]byte, len(plaintext))
+ mode.CryptBlocks(cyphertext, plaintext)
+
+ // Prepare ASN.1 Encrypted Content Info
+ eci := encryptedContentInfo{
+ ContentType: oidData,
+ ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{
+ Algorithm: oidEncryptionAlgorithmDESCBC,
+ Parameters: asn1.RawValue{Tag: 4, Bytes: iv},
+ },
+ EncryptedContent: marshalEncryptedContent(cyphertext),
+ }
+
+ return key, &eci, nil
+}
+
+// Encrypt creates and returns an envelope data PKCS7 structure with encrypted
+// recipient keys for each recipient public key.
+//
+// The algorithm used to perform encryption is determined by the current value
+// of the global ContentEncryptionAlgorithm package variable. By default, the
+// value is EncryptionAlgorithmDESCBC. To use a different algorithm, change the
+// value before calling Encrypt(). For example:
+//
+// ContentEncryptionAlgorithm = EncryptionAlgorithmAES128GCM
+//
+// TODO(fullsailor): Add support for encrypting content with other algorithms
+func Encrypt(content []byte, recipients []*x509.Certificate) ([]byte, error) {
+ var eci *encryptedContentInfo
+ var key []byte
+ var err error
+
+ // Apply chosen symmetric encryption method
+ switch ContentEncryptionAlgorithm {
+ case EncryptionAlgorithmDESCBC:
+ key, eci, err = encryptDESCBC(content)
+
+ case EncryptionAlgorithmAES128GCM:
+ key, eci, err = encryptAES128GCM(content)
+
+ default:
+ return nil, ErrUnsupportedEncryptionAlgorithm
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ // Prepare each recipient's encrypted cipher key
+ recipientInfos := make([]recipientInfo, len(recipients))
+ for i, recipient := range recipients {
+ encrypted, err := encryptKey(key, recipient)
+ if err != nil {
+ return nil, err
+ }
+ ias, err := cert2issuerAndSerial(recipient)
+ if err != nil {
+ return nil, err
+ }
+ info := recipientInfo{
+ Version: 0,
+ IssuerAndSerialNumber: ias,
+ KeyEncryptionAlgorithm: pkix.AlgorithmIdentifier{
+ Algorithm: oidEncryptionAlgorithmRSA,
+ },
+ EncryptedKey: encrypted,
+ }
+ recipientInfos[i] = info
+ }
+
+ // Prepare envelope content
+ envelope := envelopedData{
+ EncryptedContentInfo: *eci,
+ Version: 0,
+ RecipientInfos: recipientInfos,
+ }
+ innerContent, err := asn1.Marshal(envelope)
+ if err != nil {
+ return nil, err
+ }
+
+ // Prepare outer payload structure
+ wrapper := contentInfo{
+ ContentType: oidEnvelopedData,
+ Content: asn1.RawValue{Class: 2, Tag: 0, IsCompound: true, Bytes: innerContent},
+ }
+
+ return asn1.Marshal(wrapper)
+}
+
+func marshalEncryptedContent(content []byte) asn1.RawValue {
+ asn1Content, _ := asn1.Marshal(content)
+ return asn1.RawValue{Tag: 0, Class: 2, Bytes: asn1Content, IsCompound: true}
+}
+
+func encryptKey(key []byte, recipient *x509.Certificate) ([]byte, error) {
+ if pub := recipient.PublicKey.(*rsa.PublicKey); pub != nil {
+ return rsa.EncryptPKCS1v15(rand.Reader, pub, key)
+ }
+ return nil, ErrUnsupportedAlgorithm
+}
diff --git a/vendor/github.com/fullsailor/pkcs7/pkcs7_test.go b/vendor/github.com/fullsailor/pkcs7/pkcs7_test.go
new file mode 100644
index 00000000..cbe2c5ca
--- /dev/null
+++ b/vendor/github.com/fullsailor/pkcs7/pkcs7_test.go
@@ -0,0 +1,678 @@
+package pkcs7
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "encoding/pem"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "math/big"
+ "os"
+ "os/exec"
+ "testing"
+ "time"
+)
+
+func TestVerify(t *testing.T) {
+ fixture := UnmarshalTestFixture(SignedTestFixture)
+ p7, err := Parse(fixture.Input)
+ if err != nil {
+ t.Errorf("Parse encountered unexpected error: %v", err)
+ }
+
+ if err := p7.Verify(); err != nil {
+ t.Errorf("Verify failed with error: %v", err)
+ }
+ expected := []byte("We the People")
+ if bytes.Compare(p7.Content, expected) != 0 {
+ t.Errorf("Signed content does not match.\n\tExpected:%s\n\tActual:%s", expected, p7.Content)
+
+ }
+}
+
+func TestVerifyEC2(t *testing.T) {
+ fixture := UnmarshalTestFixture(EC2IdentityDocumentFixture)
+ p7, err := Parse(fixture.Input)
+ if err != nil {
+ t.Errorf("Parse encountered unexpected error: %v", err)
+ }
+ p7.Certificates = []*x509.Certificate{fixture.Certificate}
+ if err := p7.Verify(); err != nil {
+ t.Errorf("Verify failed with error: %v", err)
+ }
+}
+
+func TestVerifyAppStore(t *testing.T) {
+ fixture := UnmarshalTestFixture(AppStoreRecieptFixture)
+ p7, err := Parse(fixture.Input)
+ if err != nil {
+ t.Errorf("Parse encountered unexpected error: %v", err)
+ }
+ if err := p7.Verify(); err != nil {
+ t.Errorf("Verify failed with error: %v", err)
+ }
+}
+
+func TestDecrypt(t *testing.T) {
+ fixture := UnmarshalTestFixture(EncryptedTestFixture)
+ p7, err := Parse(fixture.Input)
+ if err != nil {
+ t.Fatal(err)
+ }
+ content, err := p7.Decrypt(fixture.Certificate, fixture.PrivateKey)
+ if err != nil {
+ t.Errorf("Cannot Decrypt with error: %v", err)
+ }
+ expected := []byte("This is a test")
+ if bytes.Compare(content, expected) != 0 {
+ t.Errorf("Decrypted result does not match.\n\tExpected:%s\n\tActual:%s", expected, content)
+ }
+}
+
+func TestDegenerateCertificate(t *testing.T) {
+ cert, err := createTestCertificate()
+ if err != nil {
+ t.Fatal(err)
+ }
+ deg, err := DegenerateCertificate(cert.Certificate.Raw)
+ if err != nil {
+ t.Fatal(err)
+ }
+ testOpenSSLParse(t, deg)
+ pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: deg})
+}
+
+// writes the cert to a temporary file and tests that openssl can read it.
+func testOpenSSLParse(t *testing.T, certBytes []byte) {
+ tmpCertFile, err := ioutil.TempFile("", "testCertificate")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(tmpCertFile.Name()) // clean up
+
+ if _, err := tmpCertFile.Write(certBytes); err != nil {
+ t.Fatal(err)
+ }
+
+ opensslCMD := exec.Command("openssl", "pkcs7", "-inform", "der", "-in", tmpCertFile.Name())
+ _, err = opensslCMD.Output()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := tmpCertFile.Close(); err != nil {
+ t.Fatal(err)
+ }
+
+}
+
+func TestSign(t *testing.T) {
+ cert, err := createTestCertificate()
+ if err != nil {
+ t.Fatal(err)
+ }
+ content := []byte("Hello World")
+ for _, testDetach := range []bool{false, true} {
+ toBeSigned, err := NewSignedData(content)
+ if err != nil {
+ t.Fatalf("Cannot initialize signed data: %s", err)
+ }
+ if err := toBeSigned.AddSigner(cert.Certificate, cert.PrivateKey, SignerInfoConfig{}); err != nil {
+ t.Fatalf("Cannot add signer: %s", err)
+ }
+ if testDetach {
+ t.Log("Testing detached signature")
+ toBeSigned.Detach()
+ } else {
+ t.Log("Testing attached signature")
+ }
+ signed, err := toBeSigned.Finish()
+ if err != nil {
+ t.Fatalf("Cannot finish signing data: %s", err)
+ }
+ pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: signed})
+ p7, err := Parse(signed)
+ if err != nil {
+ t.Fatalf("Cannot parse our signed data: %s", err)
+ }
+ if testDetach {
+ p7.Content = content
+ }
+ if bytes.Compare(content, p7.Content) != 0 {
+ t.Errorf("Our content was not in the parsed data:\n\tExpected: %s\n\tActual: %s", content, p7.Content)
+ }
+ if err := p7.Verify(); err != nil {
+ t.Errorf("Cannot verify our signed data: %s", err)
+ }
+ }
+}
+
+func ExampleSignedData() {
+ // generate a signing cert or load a key pair
+ cert, err := createTestCertificate()
+ if err != nil {
+ fmt.Printf("Cannot create test certificates: %s", err)
+ }
+
+ // Initialize a SignedData struct with content to be signed
+ signedData, err := NewSignedData([]byte("Example data to be signed"))
+ if err != nil {
+ fmt.Printf("Cannot initialize signed data: %s", err)
+ }
+
+ // Add the signing cert and private key
+ if err := signedData.AddSigner(cert.Certificate, cert.PrivateKey, SignerInfoConfig{}); err != nil {
+ fmt.Printf("Cannot add signer: %s", err)
+ }
+
+ // Call Detach() is you want to remove content from the signature
+ // and generate an S/MIME detached signature
+ signedData.Detach()
+
+ // Finish() to obtain the signature bytes
+ detachedSignature, err := signedData.Finish()
+ if err != nil {
+ fmt.Printf("Cannot finish signing data: %s", err)
+ }
+ pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: detachedSignature})
+}
+
+func TestOpenSSLVerifyDetachedSignature(t *testing.T) {
+ rootCert, err := createTestCertificateByIssuer("PKCS7 Test Root CA", nil)
+ if err != nil {
+ t.Fatalf("Cannot generate root cert: %s", err)
+ }
+ signerCert, err := createTestCertificateByIssuer("PKCS7 Test Signer Cert", rootCert)
+ if err != nil {
+ t.Fatalf("Cannot generate signer cert: %s", err)
+ }
+ content := []byte("Hello World")
+ toBeSigned, err := NewSignedData(content)
+ if err != nil {
+ t.Fatalf("Cannot initialize signed data: %s", err)
+ }
+ if err := toBeSigned.AddSigner(signerCert.Certificate, signerCert.PrivateKey, SignerInfoConfig{}); err != nil {
+ t.Fatalf("Cannot add signer: %s", err)
+ }
+ toBeSigned.Detach()
+ signed, err := toBeSigned.Finish()
+ if err != nil {
+ t.Fatalf("Cannot finish signing data: %s", err)
+ }
+
+ // write the root cert to a temp file
+ tmpRootCertFile, err := ioutil.TempFile("", "pkcs7TestRootCA")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(tmpRootCertFile.Name()) // clean up
+ fd, err := os.OpenFile(tmpRootCertFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
+ if err != nil {
+ t.Fatal(err)
+ }
+ pem.Encode(fd, &pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Certificate.Raw})
+ fd.Close()
+
+ // write the signature to a temp file
+ tmpSignatureFile, err := ioutil.TempFile("", "pkcs7Signature")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(tmpSignatureFile.Name()) // clean up
+ ioutil.WriteFile(tmpSignatureFile.Name(), signed, 0755)
+
+ // write the content to a temp file
+ tmpContentFile, err := ioutil.TempFile("", "pkcs7Content")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(tmpContentFile.Name()) // clean up
+ ioutil.WriteFile(tmpContentFile.Name(), content, 0755)
+
+ // call openssl to verify the signature on the content using the root
+ opensslCMD := exec.Command("openssl", "smime", "-verify",
+ "-in", tmpSignatureFile.Name(), "-inform", "DER",
+ "-content", tmpContentFile.Name(),
+ "-CAfile", tmpRootCertFile.Name())
+ out, err := opensslCMD.Output()
+ t.Logf("%s", out)
+ if err != nil {
+ t.Fatalf("openssl command failed with %s", err)
+ }
+}
+
+func TestEncrypt(t *testing.T) {
+ modes := []int{
+ EncryptionAlgorithmDESCBC,
+ EncryptionAlgorithmAES128GCM,
+ }
+
+ for _, mode := range modes {
+ ContentEncryptionAlgorithm = mode
+
+ plaintext := []byte("Hello Secret World!")
+ cert, err := createTestCertificate()
+ if err != nil {
+ t.Fatal(err)
+ }
+ encrypted, err := Encrypt(plaintext, []*x509.Certificate{cert.Certificate})
+ if err != nil {
+ t.Fatal(err)
+ }
+ p7, err := Parse(encrypted)
+ if err != nil {
+ t.Fatalf("cannot Parse encrypted result: %s", err)
+ }
+ result, err := p7.Decrypt(cert.Certificate, cert.PrivateKey)
+ if err != nil {
+ t.Fatalf("cannot Decrypt encrypted result: %s", err)
+ }
+ if bytes.Compare(plaintext, result) != 0 {
+ t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result)
+ }
+ }
+}
+
+func TestUnmarshalSignedAttribute(t *testing.T) {
+ cert, err := createTestCertificate()
+ if err != nil {
+ t.Fatal(err)
+ }
+ content := []byte("Hello World")
+ toBeSigned, err := NewSignedData(content)
+ if err != nil {
+ t.Fatalf("Cannot initialize signed data: %s", err)
+ }
+ oidTest := asn1.ObjectIdentifier{2, 3, 4, 5, 6, 7}
+ testValue := "TestValue"
+ if err := toBeSigned.AddSigner(cert.Certificate, cert.PrivateKey, SignerInfoConfig{
+ ExtraSignedAttributes: []Attribute{Attribute{Type: oidTest, Value: testValue}},
+ }); err != nil {
+ t.Fatalf("Cannot add signer: %s", err)
+ }
+ signed, err := toBeSigned.Finish()
+ if err != nil {
+ t.Fatalf("Cannot finish signing data: %s", err)
+ }
+ p7, err := Parse(signed)
+ var actual string
+ err = p7.UnmarshalSignedAttribute(oidTest, &actual)
+ if err != nil {
+ t.Fatalf("Cannot unmarshal test value: %s", err)
+ }
+ if testValue != actual {
+ t.Errorf("Attribute does not match test value\n\tExpected: %s\n\tActual: %s", testValue, actual)
+ }
+}
+
+func TestPad(t *testing.T) {
+ tests := []struct {
+ Original []byte
+ Expected []byte
+ BlockSize int
+ }{
+ {[]byte{0x1, 0x2, 0x3, 0x10}, []byte{0x1, 0x2, 0x3, 0x10, 0x4, 0x4, 0x4, 0x4}, 8},
+ {[]byte{0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0}, []byte{0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8}, 8},
+ }
+ for _, test := range tests {
+ padded, err := pad(test.Original, test.BlockSize)
+ if err != nil {
+ t.Errorf("pad encountered error: %s", err)
+ continue
+ }
+ if bytes.Compare(test.Expected, padded) != 0 {
+ t.Errorf("pad results mismatch:\n\tExpected: %X\n\tActual: %X", test.Expected, padded)
+ }
+ }
+}
+
+type certKeyPair struct {
+ Certificate *x509.Certificate
+ PrivateKey *rsa.PrivateKey
+}
+
+func createTestCertificate() (certKeyPair, error) {
+ signer, err := createTestCertificateByIssuer("Eddard Stark", nil)
+ if err != nil {
+ return certKeyPair{}, err
+ }
+ fmt.Println("Created root cert")
+ pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: signer.Certificate.Raw})
+ pair, err := createTestCertificateByIssuer("Jon Snow", signer)
+ if err != nil {
+ return certKeyPair{}, err
+ }
+ fmt.Println("Created signer cert")
+ pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: pair.Certificate.Raw})
+ return *pair, nil
+}
+
+func createTestCertificateByIssuer(name string, issuer *certKeyPair) (*certKeyPair, error) {
+ priv, err := rsa.GenerateKey(rand.Reader, 1024)
+ if err != nil {
+ return nil, err
+ }
+ serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 32)
+ serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
+ if err != nil {
+ return nil, err
+ }
+
+ template := x509.Certificate{
+ SerialNumber: serialNumber,
+ SignatureAlgorithm: x509.SHA256WithRSA,
+ Subject: pkix.Name{
+ CommonName: name,
+ Organization: []string{"Acme Co"},
+ },
+ NotBefore: time.Now(),
+ NotAfter: time.Now().AddDate(1, 0, 0),
+ KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection},
+ }
+ var issuerCert *x509.Certificate
+ var issuerKey crypto.PrivateKey
+ if issuer != nil {
+ issuerCert = issuer.Certificate
+ issuerKey = issuer.PrivateKey
+ } else {
+ template.IsCA = true
+ template.KeyUsage |= x509.KeyUsageCertSign
+ issuerCert = &template
+ issuerKey = priv
+ }
+ cert, err := x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.Public(), issuerKey)
+ if err != nil {
+ return nil, err
+ }
+ leaf, err := x509.ParseCertificate(cert)
+ if err != nil {
+ return nil, err
+ }
+ return &certKeyPair{
+ Certificate: leaf,
+ PrivateKey: priv,
+ }, nil
+}
+
+type TestFixture struct {
+ Input []byte
+ Certificate *x509.Certificate
+ PrivateKey *rsa.PrivateKey
+}
+
+func UnmarshalTestFixture(testPEMBlock string) TestFixture {
+ var result TestFixture
+ var derBlock *pem.Block
+ var pemBlock = []byte(testPEMBlock)
+ for {
+ derBlock, pemBlock = pem.Decode(pemBlock)
+ if derBlock == nil {
+ break
+ }
+ switch derBlock.Type {
+ case "PKCS7":
+ result.Input = derBlock.Bytes
+ case "CERTIFICATE":
+ result.Certificate, _ = x509.ParseCertificate(derBlock.Bytes)
+ case "PRIVATE KEY":
+ result.PrivateKey, _ = x509.ParsePKCS1PrivateKey(derBlock.Bytes)
+ }
+ }
+
+ return result
+}
+
+func MarshalTestFixture(t TestFixture, w io.Writer) {
+ if t.Input != nil {
+ pem.Encode(w, &pem.Block{Type: "PKCS7", Bytes: t.Input})
+ }
+ if t.Certificate != nil {
+ pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: t.Certificate.Raw})
+ }
+ if t.PrivateKey != nil {
+ pem.Encode(w, &pem.Block{Type: "PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(t.PrivateKey)})
+ }
+}
+
+var SignedTestFixture = `
+-----BEGIN PKCS7-----
+MIIDVgYJKoZIhvcNAQcCoIIDRzCCA0MCAQExCTAHBgUrDgMCGjAcBgkqhkiG9w0B
+BwGgDwQNV2UgdGhlIFBlb3BsZaCCAdkwggHVMIIBQKADAgECAgRpuDctMAsGCSqG
+SIb3DQEBCzApMRAwDgYDVQQKEwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3Rh
+cmswHhcNMTUwNTA2MDQyNDQ4WhcNMTYwNTA2MDQyNDQ4WjAlMRAwDgYDVQQKEwdB
+Y21lIENvMREwDwYDVQQDEwhKb24gU25vdzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+gYkCgYEAqr+tTF4mZP5rMwlXp1y+crRtFpuLXF1zvBZiYMfIvAHwo1ta8E1IcyEP
+J1jIiKMcwbzeo6kAmZzIJRCTezq9jwXUsKbQTvcfOH9HmjUmXBRWFXZYoQs/OaaF
+a45deHmwEeMQkuSWEtYiVKKZXtJOtflKIT3MryJEDiiItMkdybUCAwEAAaMSMBAw
+DgYDVR0PAQH/BAQDAgCgMAsGCSqGSIb3DQEBCwOBgQDK1EweZWRL+f7Z+J0kVzY8
+zXptcBaV4Lf5wGZJLJVUgp33bpLNpT3yadS++XQJ+cvtW3wADQzBSTMduyOF8Zf+
+L7TjjrQ2+F2HbNbKUhBQKudxTfv9dJHdKbD+ngCCdQJYkIy2YexsoNG0C8nQkggy
+axZd/J69xDVx6pui3Sj8sDGCATYwggEyAgEBMDEwKTEQMA4GA1UEChMHQWNtZSBD
+bzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrAgRpuDctMAcGBSsOAwIaoGEwGAYJKoZI
+hvcNAQkDMQsGCSqGSIb3DQEHATAgBgkqhkiG9w0BCQUxExcRMTUwNTA2MDAyNDQ4
+LTA0MDAwIwYJKoZIhvcNAQkEMRYEFG9D7gcTh9zfKiYNJ1lgB0yTh4sZMAsGCSqG
+SIb3DQEBAQSBgFF3sGDU9PtXty/QMtpcFa35vvIOqmWQAIZt93XAskQOnBq4OloX
+iL9Ct7t1m4pzjRm0o9nDkbaSLZe7HKASHdCqijroScGlI8M+alJ8drHSFv6ZIjnM
+FIwIf0B2Lko6nh9/6mUXq7tbbIHa3Gd1JUVire/QFFtmgRXMbXYk8SIS
+-----END PKCS7-----
+-----BEGIN CERTIFICATE-----
+MIIB1TCCAUCgAwIBAgIEabg3LTALBgkqhkiG9w0BAQswKTEQMA4GA1UEChMHQWNt
+ZSBDbzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrMB4XDTE1MDUwNjA0MjQ0OFoXDTE2
+MDUwNjA0MjQ0OFowJTEQMA4GA1UEChMHQWNtZSBDbzERMA8GA1UEAxMISm9uIFNu
+b3cwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKq/rUxeJmT+azMJV6dcvnK0
+bRabi1xdc7wWYmDHyLwB8KNbWvBNSHMhDydYyIijHMG83qOpAJmcyCUQk3s6vY8F
+1LCm0E73Hzh/R5o1JlwUVhV2WKELPzmmhWuOXXh5sBHjEJLklhLWIlSimV7STrX5
+SiE9zK8iRA4oiLTJHcm1AgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIAoDALBgkqhkiG
+9w0BAQsDgYEAytRMHmVkS/n+2fidJFc2PM16bXAWleC3+cBmSSyVVIKd926SzaU9
+8mnUvvl0CfnL7Vt8AA0MwUkzHbsjhfGX/i+04460Nvhdh2zWylIQUCrncU37/XSR
+3Smw/p4AgnUCWJCMtmHsbKDRtAvJ0JIIMmsWXfyevcQ1ceqbot0o/LA=
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIICXgIBAAKBgQCqv61MXiZk/mszCVenXL5ytG0Wm4tcXXO8FmJgx8i8AfCjW1rw
+TUhzIQ8nWMiIoxzBvN6jqQCZnMglEJN7Or2PBdSwptBO9x84f0eaNSZcFFYVdlih
+Cz85poVrjl14ebAR4xCS5JYS1iJUople0k61+UohPcyvIkQOKIi0yR3JtQIDAQAB
+AoGBAIPLCR9N+IKxodq11lNXEaUFwMHXc1zqwP8no+2hpz3+nVfplqqubEJ4/PJY
+5AgbJoIfnxVhyBXJXu7E+aD/OPneKZrgp58YvHKgGvvPyJg2gpC/1Fh0vQB0HNpI
+1ZzIZUl8ZTUtVgtnCBUOh5JGI4bFokAqrT//Uvcfd+idgxqBAkEA1ZbP/Kseld14
+qbWmgmU5GCVxsZRxgR1j4lG3UVjH36KXMtRTm1atAam1uw3OEGa6Y3ANjpU52FaB
+Hep5rkk4FQJBAMynMo1L1uiN5GP+KYLEF5kKRxK+FLjXR0ywnMh+gpGcZDcOae+J
++t1gLoWBIESH/Xt639T7smuSfrZSA9V0EyECQA8cvZiWDvLxmaEAXkipmtGPjKzQ
+4PsOtkuEFqFl07aKDYKmLUg3aMROWrJidqsIabWxbvQgsNgSvs38EiH3wkUCQQCg
+ndxb7piVXb9RBwm3OoU2tE1BlXMX+sVXmAkEhd2dwDsaxrI3sHf1xGXem5AimQRF
+JBOFyaCnMotGNioSHY5hAkEAxyXcNixQ2RpLXJTQZtwnbk0XDcbgB+fBgXnv/4f3
+BCvcu85DqJeJyQv44Oe1qsXEX9BfcQIOVaoep35RPlKi9g==
+-----END PRIVATE KEY-----`
+
+// Content is "This is a test"
+var EncryptedTestFixture = `
+-----BEGIN PKCS7-----
+MIIBFwYJKoZIhvcNAQcDoIIBCDCCAQQCAQAxgcowgccCAQAwMjApMRAwDgYDVQQK
+EwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3RhcmsCBQDL+CvWMAsGCSqGSIb3
+DQEBAQSBgKyP/5WlRTZD3dWMrLOX6QRNDrXEkQjhmToRwFZdY3LgUh25ZU0S/q4G
+dHPV21Fv9lQD+q7l3vfeHw8M6Z1PKi9sHMVfxAkQpvaI96DTIT3YHtuLC1w3geCO
+8eFWTq2qS4WChSuS/yhYosjA1kTkE0eLnVZcGw0z/WVuEZznkdyIMDIGCSqGSIb3
+DQEHATARBgUrDgMCBwQImpKsUyMPpQigEgQQRcWWrCRXqpD5Njs0GkJl+g==
+-----END PKCS7-----
+-----BEGIN CERTIFICATE-----
+MIIB1jCCAUGgAwIBAgIFAMv4K9YwCwYJKoZIhvcNAQELMCkxEDAOBgNVBAoTB0Fj
+bWUgQ28xFTATBgNVBAMTDEVkZGFyZCBTdGFyazAeFw0xNTA1MDYwMzU2NDBaFw0x
+NjA1MDYwMzU2NDBaMCUxEDAOBgNVBAoTB0FjbWUgQ28xETAPBgNVBAMTCEpvbiBT
+bm93MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK6NU0R0eiCYVquU4RcjKc
+LzGfx0aa1lMr2TnLQUSeLFZHFxsyyMXXuMPig3HK4A7SGFHupO+/1H/sL4xpH5zg
+8+Zg2r8xnnney7abxcuv0uATWSIeKlNnb1ZO1BAxFnESc3GtyOCr2dUwZHX5mRVP
++Zxp2ni5qHNraf3wE2VPIQIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCAKAwCwYJKoZI
+hvcNAQELA4GBAIr2F7wsqmEU/J/kLyrCgEVXgaV/sKZq4pPNnzS0tBYk8fkV3V18
+sBJyHKRLL/wFZASvzDcVGCplXyMdAOCyfd8jO3F9Ac/xdlz10RrHJT75hNu3a7/n
+9KNwKhfN4A1CQv2x372oGjRhCW5bHNCWx4PIVeNzCyq/KZhyY9sxHE6f
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIICXgIBAAKBgQDK6NU0R0eiCYVquU4RcjKcLzGfx0aa1lMr2TnLQUSeLFZHFxsy
+yMXXuMPig3HK4A7SGFHupO+/1H/sL4xpH5zg8+Zg2r8xnnney7abxcuv0uATWSIe
+KlNnb1ZO1BAxFnESc3GtyOCr2dUwZHX5mRVP+Zxp2ni5qHNraf3wE2VPIQIDAQAB
+AoGBALyvnSt7KUquDen7nXQtvJBudnf9KFPt//OjkdHHxNZNpoF/JCSqfQeoYkeu
+MdAVYNLQGMiRifzZz4dDhA9xfUAuy7lcGQcMCxEQ1dwwuFaYkawbS0Tvy2PFlq2d
+H5/HeDXU4EDJ3BZg0eYj2Bnkt1sJI35UKQSxblQ0MY2q0uFBAkEA5MMOogkgUx1C
+67S1tFqMUSM8D0mZB0O5vOJZC5Gtt2Urju6vywge2ArExWRXlM2qGl8afFy2SgSv
+Xk5eybcEiQJBAOMRwwbEoW5NYHuFFbSJyWll4n71CYuWuQOCzehDPyTb80WFZGLV
+i91kFIjeERyq88eDE5xVB3ZuRiXqaShO/9kCQQCKOEkpInaDgZSjskZvuJ47kByD
+6CYsO4GIXQMMeHML8ncFH7bb6AYq5ybJVb2NTU7QLFJmfeYuhvIm+xdOreRxAkEA
+o5FC5Jg2FUfFzZSDmyZ6IONUsdF/i78KDV5nRv1R+hI6/oRlWNCtTNBv/lvBBd6b
+dseUE9QoaQZsn5lpILEvmQJAZ0B+Or1rAYjnbjnUhdVZoy9kC4Zov+4UH3N/BtSy
+KJRWUR0wTWfZBPZ5hAYZjTBEAFULaYCXlQKsODSp0M1aQA==
+-----END PRIVATE KEY-----`
+
+var EC2IdentityDocumentFixture = `
+-----BEGIN PKCS7-----
+MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAaCA
+JIAEggGmewogICJwcml2YXRlSXAiIDogIjE3Mi4zMC4wLjI1MiIsCiAgImRldnBh
+eVByb2R1Y3RDb2RlcyIgOiBudWxsLAogICJhdmFpbGFiaWxpdHlab25lIiA6ICJ1
+cy1lYXN0LTFhIiwKICAidmVyc2lvbiIgOiAiMjAxMC0wOC0zMSIsCiAgImluc3Rh
+bmNlSWQiIDogImktZjc5ZmU1NmMiLAogICJiaWxsaW5nUHJvZHVjdHMiIDogbnVs
+bCwKICAiaW5zdGFuY2VUeXBlIiA6ICJ0Mi5taWNybyIsCiAgImFjY291bnRJZCIg
+OiAiMTIxNjU5MDE0MzM0IiwKICAiaW1hZ2VJZCIgOiAiYW1pLWZjZTNjNjk2IiwK
+ICAicGVuZGluZ1RpbWUiIDogIjIwMTYtMDQtMDhUMDM6MDE6MzhaIiwKICAiYXJj
+aGl0ZWN0dXJlIiA6ICJ4ODZfNjQiLAogICJrZXJuZWxJZCIgOiBudWxsLAogICJy
+YW1kaXNrSWQiIDogbnVsbCwKICAicmVnaW9uIiA6ICJ1cy1lYXN0LTEiCn0AAAAA
+AAAxggEYMIIBFAIBATBpMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5n
+dG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2Vi
+IFNlcnZpY2VzIExMQwIJAJa6SNnlXhpnMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0B
+CQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNjA0MDgwMzAxNDRaMCMG
+CSqGSIb3DQEJBDEWBBTuUc28eBXmImAautC+wOjqcFCBVjAJBgcqhkjOOAQDBC8w
+LQIVAKA54NxGHWWCz5InboDmY/GHs33nAhQ6O/ZI86NwjA9Vz3RNMUJrUPU5tAAA
+AAAAAA==
+-----END PKCS7-----
+-----BEGIN CERTIFICATE-----
+MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw
+FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD
+VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z
+ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u
+IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl
+cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e
+ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3
+VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P
+hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j
+k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U
+hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF
+lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf
+MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW
+MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw
+vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw
+7HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K
+-----END CERTIFICATE-----`
+
+var AppStoreRecieptFixture = `
+-----BEGIN PKCS7-----
+MIITtgYJKoZIhvcNAQcCoIITpzCCE6MCAQExCzAJBgUrDgMCGgUAMIIDVwYJKoZI
+hvcNAQcBoIIDSASCA0QxggNAMAoCAQgCAQEEAhYAMAoCARQCAQEEAgwAMAsCAQEC
+AQEEAwIBADALAgEDAgEBBAMMATEwCwIBCwIBAQQDAgEAMAsCAQ8CAQEEAwIBADAL
+AgEQAgEBBAMCAQAwCwIBGQIBAQQDAgEDMAwCAQoCAQEEBBYCNCswDAIBDgIBAQQE
+AgIAjTANAgENAgEBBAUCAwFgvTANAgETAgEBBAUMAzEuMDAOAgEJAgEBBAYCBFAy
+NDcwGAIBAgIBAQQQDA5jb20uemhpaHUudGVzdDAYAgEEAgECBBCS+ZODNMHwT1Nz
+gWYDXyWZMBsCAQACAQEEEwwRUHJvZHVjdGlvblNhbmRib3gwHAIBBQIBAQQU4nRh
+YCEZx70Flzv7hvJRjJZckYIwHgIBDAIBAQQWFhQyMDE2LTA3LTIzVDA2OjIxOjEx
+WjAeAgESAgEBBBYWFDIwMTMtMDgtMDFUMDc6MDA6MDBaMD0CAQYCAQEENbR21I+a
+8+byMXo3NPRoDWQmSXQF2EcCeBoD4GaL//ZCRETp9rGFPSg1KekCP7Kr9HAqw09m
+MEICAQcCAQEEOlVJozYYBdugybShbiiMsejDMNeCbZq6CrzGBwW6GBy+DGWxJI91
+Y3ouXN4TZUhuVvLvN1b0m5T3ggQwggFaAgERAgEBBIIBUDGCAUwwCwICBqwCAQEE
+AhYAMAsCAgatAgEBBAIMADALAgIGsAIBAQQCFgAwCwICBrICAQEEAgwAMAsCAgaz
+AgEBBAIMADALAgIGtAIBAQQCDAAwCwICBrUCAQEEAgwAMAsCAga2AgEBBAIMADAM
+AgIGpQIBAQQDAgEBMAwCAgarAgEBBAMCAQEwDAICBq4CAQEEAwIBADAMAgIGrwIB
+AQQDAgEAMAwCAgaxAgEBBAMCAQAwGwICBqcCAQEEEgwQMTAwMDAwMDIyNTMyNTkw
+MTAbAgIGqQIBAQQSDBAxMDAwMDAwMjI1MzI1OTAxMB8CAgaoAgEBBBYWFDIwMTYt
+MDctMjNUMDY6MjE6MTFaMB8CAgaqAgEBBBYWFDIwMTYtMDctMjNUMDY6MjE6MTFa
+MCACAgamAgEBBBcMFWNvbS56aGlodS50ZXN0LnRlc3RfMaCCDmUwggV8MIIEZKAD
+AgECAggO61eH554JjTANBgkqhkiG9w0BAQUFADCBljELMAkGA1UEBhMCVVMxEzAR
+BgNVBAoMCkFwcGxlIEluYy4xLDAqBgNVBAsMI0FwcGxlIFdvcmxkd2lkZSBEZXZl
+bG9wZXIgUmVsYXRpb25zMUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxv
+cGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNTExMTMw
+MjE1MDlaFw0yMzAyMDcyMTQ4NDdaMIGJMTcwNQYDVQQDDC5NYWMgQXBwIFN0b3Jl
+IGFuZCBpVHVuZXMgU3RvcmUgUmVjZWlwdCBTaWduaW5nMSwwKgYDVQQLDCNBcHBs
+ZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczETMBEGA1UECgwKQXBwbGUg
+SW5jLjELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQClz4H9JaKBW9aH7SPaMxyO4iPApcQmyz3Gn+xKDVWG/6QC15fKOVRtfX+yVBid
+xCxScY5ke4LOibpJ1gjltIhxzz9bRi7GxB24A6lYogQ+IXjV27fQjhKNg0xbKmg3
+k8LyvR7E0qEMSlhSqxLj7d0fmBWQNS3CzBLKjUiB91h4VGvojDE2H0oGDEdU8zeQ
+uLKSiX1fpIVK4cCc4Lqku4KXY/Qrk8H9Pm/KwfU8qY9SGsAlCnYO3v6Z/v/Ca/Vb
+XqxzUUkIVonMQ5DMjoEC0KCXtlyxoWlph5AQaCYmObgdEHOwCl3Fc9DfdjvYLdmI
+HuPsB8/ijtDT+iZVge/iA0kjAgMBAAGjggHXMIIB0zA/BggrBgEFBQcBAQQzMDEw
+LwYIKwYBBQUHMAGGI2h0dHA6Ly9vY3NwLmFwcGxlLmNvbS9vY3NwMDMtd3dkcjA0
+MB0GA1UdDgQWBBSRpJz8xHa3n6CK9E31jzZd7SsEhTAMBgNVHRMBAf8EAjAAMB8G
+A1UdIwQYMBaAFIgnFwmpthhgi+zruvZHWcVSVKO3MIIBHgYDVR0gBIIBFTCCAREw
+ggENBgoqhkiG92NkBQYBMIH+MIHDBggrBgEFBQcCAjCBtgyBs1JlbGlhbmNlIG9u
+IHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5j
+ZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25k
+aXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0
+aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMDYGCCsGAQUFBwIBFipodHRwOi8vd3d3
+LmFwcGxlLmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eS8wDgYDVR0PAQH/BAQDAgeA
+MBAGCiqGSIb3Y2QGCwEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQANphvTLj3jWysH
+bkKWbNPojEMwgl/gXNGNvr0PvRr8JZLbjIXDgFnf4+LXLgUUrA3btrj+/DUufMut
+F2uOfx/kd7mxZ5W0E16mGYZ2+FogledjjA9z/Ojtxh+umfhlSFyg4Cg6wBA3Lbmg
+BDkfc7nIBf3y3n8aKipuKwH8oCBc2et9J6Yz+PWY4L5E27FMZ/xuCk/J4gao0pfz
+p45rUaJahHVl0RYEYuPBX/UIqc9o2ZIAycGMs/iNAGS6WGDAfK+PdcppuVsq1h1o
+bphC9UynNxmbzDscehlD86Ntv0hgBgw2kivs3hi1EdotI9CO/KBpnBcbnoB7OUdF
+MGEvxxOoMIIEIjCCAwqgAwIBAgIIAd68xDltoBAwDQYJKoZIhvcNAQEFBQAwYjEL
+MAkGA1UEBhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAkBgNVBAsTHUFwcGxl
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1BcHBsZSBSb290IENB
+MB4XDTEzMDIwNzIxNDg0N1oXDTIzMDIwNzIxNDg0N1owgZYxCzAJBgNVBAYTAlVT
+MRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3JsZHdpZGUg
+RGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3aWRlIERl
+dmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKOFSmy1aqyCQ5SOmM7uxfuH8mkbw0
+U3rOfGOAYXdkXqUHI7Y5/lAtFVZYcC1+xG7BSoU+L/DehBqhV8mvexj/avoVEkkV
+CBmsqtsqMu2WY2hSFT2Miuy/axiV4AOsAX2XBWfODoWVN2rtCbauZ81RZJ/GXNG8
+V25nNYB2NqSHgW44j9grFU57Jdhav06DwY3Sk9UacbVgnJ0zTlX5ElgMhrgWDcHl
+d0WNUEi6Ky3klIXh6MSdxmilsKP8Z35wugJZS3dCkTm59c3hTO/AO0iMpuUhXf1q
+arunFjVg0uat80YpyejDi+l5wGphZxWy8P3laLxiX27Pmd3vG2P+kmWrAgMBAAGj
+gaYwgaMwHQYDVR0OBBYEFIgnFwmpthhgi+zruvZHWcVSVKO3MA8GA1UdEwEB/wQF
+MAMBAf8wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wLgYDVR0fBCcw
+JTAjoCGgH4YdaHR0cDovL2NybC5hcHBsZS5jb20vcm9vdC5jcmwwDgYDVR0PAQH/
+BAQDAgGGMBAGCiqGSIb3Y2QGAgEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQBPz+9Z
+viz1smwvj+4ThzLoBTWobot9yWkMudkXvHcs1Gfi/ZptOllc34MBvbKuKmFysa/N
+w0Uwj6ODDc4dR7Txk4qjdJukw5hyhzs+r0ULklS5MruQGFNrCk4QttkdUGwhgAqJ
+TleMa1s8Pab93vcNIx0LSiaHP7qRkkykGRIZbVf1eliHe2iK5IaMSuviSRSqpd1V
+AKmuu0swruGgsbwpgOYJd+W+NKIByn/c4grmO7i77LpilfMFY0GCzQ87HUyVpNur
++cmV6U/kTecmmYHpvPm0KdIBembhLoz2IYrF+Hjhga6/05Cdqa3zr/04GpZnMBxR
+pVzscYqCtGwPDBUfMIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQsw
+CQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0Ew
+HhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzET
+MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv
+biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne
++Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjcz
+y8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQ
+Z48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCS
+C7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINB
+hzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIB
+djAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9Bp
+R5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/
+CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcC
+ARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCB
+thqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFz
+c3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJk
+IHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5
+IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3
+DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizU
+sZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJ
+fBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr
+1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltk
+wGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIq
+xw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUhMYIByzCCAccCAQEwgaMwgZYxCzAJ
+BgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBX
+b3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29y
+bGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkCCA7rV4fnngmNMAkGBSsOAwIaBQAwDQYJKoZIhvcNAQEBBQAEggEAasPtnide
+NWyfUtewW9OSgcQA8pW+5tWMR0469cBPZR84uJa0gyfmPspySvbNOAwnrwzZHYLa
+ujOxZLip4DUw4F5s3QwUa3y4BXpF4J+NSn9XNvxNtnT/GcEQtCuFwgJ0o3F0ilhv
+MTHrwiwyx/vr+uNDqlORK8lfK+1qNp+A/kzh8eszMrn4JSeTh9ZYxLHE56WkTQGD
+VZXl0gKgxSOmDrcp1eQxdlymzrPv9U60wUJ0bkPfrU9qZj3mJrmrkQk61JTe3j6/
+QfjfFBG9JG2mUmYQP1KQ3SypGHzDW8vngvsGu//tNU0NFfOqQu4bYU4VpQl0nPtD
+4B85NkrgvQsWAQ==
+-----END PKCS7-----`