This repository has been archived by the owner on Sep 19, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 22
/
security.go
164 lines (146 loc) · 4.97 KB
/
security.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package eqip
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"fmt"
"io/ioutil"
"time"
"unicode"
"github.com/satori/go.uuid"
)
const (
defaultAuthNamespace = "urn:gov.opm.eqip.ws/Authentication"
)
// generateSecurityToken creates a security token that is sent across with the SOAP request.
// The security token consists of three components: the header, a time stamp, and a long unique identifier string.
//
// The three fields are generated as follows:
// Header
// Always the two letters "WS" and is case sensitive.
// Time Stamp
// The current time in milliseconds as generated by the Java System.currentTimeMillis() method
// Unique Identifier
// This portion of the token consists of a long unique string. The e-QIP client creates this string by
// generating two UUIDs using different algorithms, and then concatenating them together with a "-".
// The three token components are concatenated together with a "_" to create a token like the following:
// WS_1393440378647_520ffdca-a179-4771-9fea-0a959ecb1a3e-e29376a3-3291-30b1-a96a-f1508a5dc628
// This uses the go.uuid package which generates UUID's that are RFC 4122 compliant
func generateSecurityToken() string {
header := "WS"
// The client uses the Java System.currentTimeMillis() so we must convert our Nano time to milliseconds
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
u1 := uuid.NewV1()
u2 := uuid.NewV4()
return fmt.Sprintf("%s_%v_%s-%s", header, timestamp, u1.String(), u2.String())
}
// signSecurityToken takes a token and signs the contents using the
// private key file encoded as PKCS#8 DER format.
func signSecurityToken(token, privateKeyPath string) ([]byte, error) {
// Read the private key file
b, err := ioutil.ReadFile(privateKeyPath)
if err != nil {
return nil, err
}
// Parse the private key file
cert, err := x509.ParsePKCS8PrivateKey(b)
if err != nil {
return nil, err
}
// ParsePKCS8PrivateKey returns an interface{} so we must cast to rsa.PrivateKey
rsaKey, ok := cert.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("Unable to cast to PrivateKey")
}
// Apply the hex hash
unsignedBytes, err := hexHash(token)
if err != nil {
return nil, err
}
// Execute sha256 hash on the unsignedBytes
hashed := sha256.Sum256(unsignedBytes)
// Sign the content
rng := rand.Reader
signature, err := rsaKey.Sign(rng, hashed[:], crypto.SHA256)
if err != nil {
return nil, err
}
signatureHex := make([]byte, hex.EncodedLen(len(signature)))
hex.Encode(signatureHex, signature)
return signatureHex, nil
}
// verifySecurityToken validates that the security token information
// was generated properly and that it was signed by the corresponding
// certificate. This is meant for local testing and will not be used
// in production because this is what the webservice server will be validating.
func verifySecurityToken(unsignedToken, signedToken []byte, publicCertPath string) error {
// Read in public certificate
b, err := ioutil.ReadFile(publicCertPath)
if err != nil {
return err
}
// Parse certificate
block, _ := pem.Decode(b)
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return err
}
// The signed signature is hex encoded so we must decode
decoded := make([]byte, hex.DecodedLen(len(signedToken)))
_, err = hex.Decode(decoded, signedToken)
if err != nil {
return err
}
pk := cert.PublicKey.(*rsa.PublicKey)
// We need to process our unsigned token
originalHexed, err := hexHash(string(unsignedToken))
if err != nil {
return err
}
// Hash the unsigned token using sha256
hashed := sha256.Sum256(originalHexed)
err = rsa.VerifyPKCS1v15(pk, crypto.SHA256, hashed[:], decoded)
return err
}
// hexHash takes a string and decodes each character into a hex value. This
// function was translated from code that was provided in the eqip Web Services
// Documentation. The original method name was HexDecode which was slightly misleading
// because it allows invalid hex values to be processed.
func hexHash(hash string) ([]byte, error) {
var buf bytes.Buffer
for i := 0; i < len(hash)-1; i += 2 {
hByte := rune(hash[i])
lByte := rune(hash[i+1])
byteInt := int8(characterDigitRadix(hByte, 16))
byteInt = (byteInt << 4)
byteInt += int8(characterDigitRadix(lByte, 16))
if err := buf.WriteByte(byte(byteInt)); err != nil {
return nil, err
}
}
return buf.Bytes(), nil
}
// characterDigitRadix returns the numeric value of the rune in the specified radix. This
// logic was translated from code that was provided in the eqip Web Services Documentation.
// This logic stems from the Java Character.Digit(char, int) located at
// https://docs.oracle.com/javase/7/docs/api/java/lang/Character.html#digit(char,%20int)
func characterDigitRadix(r rune, radix int) int {
val := -1
switch {
case unicode.IsDigit(r):
val = int(r - '0')
case unicode.IsLower(r):
val = int(r-'a') + 10
case unicode.IsUpper(r):
val = int(r-'A') + 10
}
if val >= radix {
return -1
}
return val
}