Skip to content

Instantly share code, notes, and snippets.

@philpennock
Created January 1, 2015 07:27
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save philpennock/b73ccc58c2d36b414554 to your computer and use it in GitHub Desktop.
Save philpennock/b73ccc58c2d36b414554 to your computer and use it in GitHub Desktop.
Query the NIST randomness beacon, in Go
// Author: Phil Pennock
// Based on publicly available data, as described below, so the only creative
// input was the expression in Golang.
package main
/*
An overview is at <http://www.nist.gov/itl/csd/ct/nist_beacon.cfm> and while
the REST API is reliably available (in my experience) the content at
<https://beacon.nist.gov/home> is not, so I'll replicate the data here from
when it was available:
This prototype implementation generates full-entropy bit-strings and posts
them in blocks of 512 bits every 60 seconds. Each such value is
sequence-numbered, time-stamped and signed, and includes the hash of the
previous value to chain the sequence of values together and prevent even the
source to retroactively change an output package without being detected.
Currently implemented calls are listed below. Users submitting a request need
to provide the record generation time in POSIX format (number of seconds
since midnight UTC, January 1, 1970 (see
http://en.wikipedia.org/wiki/Unix_time for more information and
http://www.epochconverter.com for an online timestamp converter.)
Current Record (or next closest):
https://beacon.nist.gov/rest/record/<timestamp>
Previous Record:
https://beacon.nist.gov/rest/record/previous/<timestamp>
Next Record:
https://beacon.nist.gov/rest/record/next/<timestamp>
Last Record:
https://beacon.nist.gov/rest/record/last
Start Chain Record:
https://beacon.nist.gov/rest/record/start-chain/<timestamp>
If a request for a next or previous record results in no record found, a 404
response is returned.
Schema
The data source schema for the NIST Beacon REST API described above can be
viewed by clicking here (<https://beacon.nist.gov/record/0.1/beacon-0.1.0.xsd>).
Certificate
NIST Beacon is currently using an X.509 certificate with the Federal Common
Policy CA as the ultimate root authority. The certificate is available here
(<https://beacon.nist.gov/certificate/beacon.cer>).
*/
// heavily influenced by http://hackaday.com/2014/12/19/nist-randomness-beacon/
// both in selecting a source test data item and in understanding what data is
// present and how signature verification should be performed. In part because
// the beacon.nist.gov/home documentation page was unavailable for some time,
// when I first started writing this.
import (
"bytes"
"crypto/x509"
"encoding/binary"
"encoding/hex"
"encoding/pem"
"encoding/xml"
"flag"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"strconv"
"time"
)
// Retrieved from https://beacon.nist.gov/certificate/beacon.cer on 2014-12-31
// using link from http://hackaday.com/2014/12/19/nist-randomness-beacon/
// Subject: C=US, O=U.S. Government, OU=Department of Commerce, OU=National Institute of Standards and Technology, OU=Devices, CN=beacon.nist.gov
// Issuer: C=US, O=Entrust, OU=Certification Authorities, OU=Entrust Managed Services SSP CA
//
const beaconCertPEM = `
-----BEGIN CERTIFICATE-----
MIIHZTCCBk2gAwIBAgIESTWNPjANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQGEwJV
UzEQMA4GA1UEChMHRW50cnVzdDEiMCAGA1UECxMZQ2VydGlmaWNhdGlvbiBBdXRo
b3JpdGllczEoMCYGA1UECxMfRW50cnVzdCBNYW5hZ2VkIFNlcnZpY2VzIFNTUCBD
QTAeFw0xNDA1MDcxMzQ4MzZaFw0xNzA1MDcxNDE4MzZaMIGtMQswCQYDVQQGEwJV
UzEYMBYGA1UEChMPVS5TLiBHb3Zlcm5tZW50MR8wHQYDVQQLExZEZXBhcnRtZW50
IG9mIENvbW1lcmNlMTcwNQYDVQQLEy5OYXRpb25hbCBJbnN0aXR1dGUgb2YgU3Rh
bmRhcmRzIGFuZCBUZWNobm9sb2d5MRAwDgYDVQQLEwdEZXZpY2VzMRgwFgYDVQQD
Ew9iZWFjb24ubmlzdC5nb3YwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQC/m2xcckaSYztt6/6YezaUmqIqY5CLvrfO2esEIJyFg+cv7S7exL3hGYeDCnQL
VtUIGViAnO9yCXDC2Kymen+CekU7WEtSB96xz/xGrY3mbwjS46QSOND9xSRMroF9
xbgqXxzJ7rL/0RMUkku3uurGb/cxUpzKt6ra7iUnzkk3BBk73kr2OXFyYYbtrN71
s0B9qKKJZuPQqmA5n80Xc3E2YbaoAW4/gesncFNL7Sdxw9NIA1L4feu/o8xp3FNP
pv2e25C0113x+yagvb1W0mw6ISwAKhJ+6G4t4hFejl7RujuiDfORgzIhHMR4CyWt
PZFVn2qxZuVooj1+mduLIXhDAgMBAAGjggPKMIIDxjAOBgNVHQ8BAf8EBAMCBsAw
FwYDVR0gBBAwDjAMBgpghkgBZQMCAQMHMIIBXgYIKwYBBQUHAQEEggFQMIIBTDCB
uAYIKwYBBQUHMAKGgatsZGFwOi8vc3NwZGlyLm1hbmFnZWQuZW50cnVzdC5jb20v
b3U9RW50cnVzdCUyME1hbmFnZWQlMjBTZXJ2aWNlcyUyMFNTUCUyMENBLG91PUNl
cnRpZmljYXRpb24lMjBBdXRob3JpdGllcyxvPUVudHJ1c3QsYz1VUz9jQUNlcnRp
ZmljYXRlO2JpbmFyeSxjcm9zc0NlcnRpZmljYXRlUGFpcjtiaW5hcnkwSwYIKwYB
BQUHMAKGP2h0dHA6Ly9zc3B3ZWIubWFuYWdlZC5lbnRydXN0LmNvbS9BSUEvQ2Vy
dHNJc3N1ZWRUb0VNU1NTUENBLnA3YzBCBggrBgEFBQcwAYY2aHR0cDovL29jc3Au
bWFuYWdlZC5lbnRydXN0LmNvbS9PQ1NQL0VNU1NTUENBUmVzcG9uZGVyMBsGA1Ud
CQQUMBIwEAYJKoZIhvZ9B0QdMQMCASIwggGHBgNVHR8EggF+MIIBejCB6qCB56CB
5IaBq2xkYXA6Ly9zc3BkaXIubWFuYWdlZC5lbnRydXN0LmNvbS9jbj1XaW5Db21i
aW5lZDEsb3U9RW50cnVzdCUyME1hbmFnZWQlMjBTZXJ2aWNlcyUyMFNTUCUyMENB
LG91PUNlcnRpZmljYXRpb24lMjBBdXRob3JpdGllcyxvPUVudHJ1c3QsYz1VUz9j
ZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0O2JpbmFyeYY0aHR0cDovL3NzcHdlYi5t
YW5hZ2VkLmVudHJ1c3QuY29tL0NSTHMvRU1TU1NQQ0ExLmNybDCBiqCBh6CBhKSB
gTB/MQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRW50cnVzdDEiMCAGA1UECxMZQ2Vy
dGlmaWNhdGlvbiBBdXRob3JpdGllczEoMCYGA1UECxMfRW50cnVzdCBNYW5hZ2Vk
IFNlcnZpY2VzIFNTUCBDQTEQMA4GA1UEAxMHQ1JMNjY3MzArBgNVHRAEJDAigA8y
MDE0MDUwNzEzNDgzNlqBDzIwMTYwNjEyMTgxODM2WjAfBgNVHSMEGDAWgBTTzudb
iafNbJHGZzapWHIJ7OI58zAdBgNVHQ4EFgQUGIOcf6r7Z9wk+2/YuG5oTs7Qwk8w
CQYDVR0TBAIwADAZBgkqhkiG9n0HQQAEDDAKGwRWOC4xAwIEsDANBgkqhkiG9w0B
AQsFAAOCAQEASc+lZBbJWsHB2WnaBr8ZfBqpgS51Eh+wLchgIq7JHhVn+LagkR8C
XmvP57a0L/E+MRBqvH2RMqwthEcjXio2WIu/lyKZmg2go9driU6H3s89X8snblDF
1B+iL73vhkLVdHXgStMS8AHbm+3BW6yjHens1tVmKSowg1P/bGT3Z4nmamdY9oLm
9sCgFccthC1BQqtPv1XsmLshJ9vmBbYMsjKq4PmS0aLA59J01YMSq4U1kzcNS7wI
1/YfUrfeV+r+j7LKBgNQTZ80By2cfSalEqCe8oxqViAz6DsfPCBeE57diZNLiJmj
a2wWIBquIAXxvD8w2Bue7pZVeUHls5V5dA==
-----END CERTIFICATE-----
`
// https://beacon.nist.gov/rest/record/1400878200
const debugRecord = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><record><version>Version 1.0</version><frequency>60</frequency><timeStamp>1400878200</timeStamp><seedValue>C4E0E995111B9986658709F759E57650859DB3DA5330F007CE9732BA2E30B1F8475E3882796DA835CBDBA6FD2D4F5345B4BB46A5F60AAC249C7D4E4670881E71</seedValue><previousOutputValue>D24EEBA206D05196C0ADC3DA2F56EA295CDD4D1C3E4D5265CCF762392CF8A4BCABA295DB441E6B9E661727FB1C37CE9DEC6D9465513356A13F515EC0CDB82D2C</previousOutputValue><signatureValue>AF8332249B8E7EBEDD0326740583FF2280FE788167F2E76C172A38858BFD3A346F5C87D879BC790BEE104CD96B5743B3E2C6B5E244C880C9362B85112C69B309277A1A97F970CC475864CF56A8A8430AFF61A8D89B2B9F537BC293E0944DAA054B77390A0D8E7844C25BDF8164D34D58C1E8DE503B62E55B311798072E276FE56F5C294FA76BE3A2BC47576BE5804A9AAC8307066C613C8507A459C898B25B502B975E5B17EAEF74F219C5C3C979E7188DDC473901EADA236C6127ABF72A2C258E95F90A4BF6F67EFAD8D66DC19C169B543B3F0F12A2D520A7E6489CE2509930592D50CD663961C10F1F2584BD89F79FBA0C0980F00D062F1ECC51A339E6B11F</signatureValue><outputValue>68ACCF41E370AA4AC3F83CCF7DA56AE8E87AA6EE491E68C2A92C661D1267697AA21FDFAE17D0701A49AA9EAA74016B4A4AD1DDD45D62961141E9C7C8FBD1FA6A</outputValue><statusCode>0</statusCode></record>`
const debugTimestamp = 1400878200
// beaconCertificate gives us the beacon certificate as a Golang object; it
// always succeeds, because the beacon is a const encoded herein, so a failure
// is panic-worthy.
func beaconCertificate() *x509.Certificate {
pemBlock, remainder := pem.Decode([]byte(beaconCertPEM))
if len(remainder) > 0 {
panic(fmt.Sprintf("have %d bytes left in PEM", len(remainder)))
}
certChain, err := x509.ParseCertificates(pemBlock.Bytes)
if err != nil {
panic(err.Error())
}
if len(certChain) != 1 {
panic(fmt.Sprintf("have %d certificates in beacon PEM, not 1", len(certChain)))
}
return certChain[0]
}
func fetchRecord(timestamp int64) ([]byte, error) {
if timestamp == 0 {
timestamp = debugTimestamp
return []byte(debugRecord), nil
}
resp, err := http.Get(fmt.Sprintf("https://beacon.nist.gov/rest/record/%d", timestamp))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
return body, err
}
type BeaconData struct {
XMLName xml.Name `xml:"record"`
Version string `xml:"version"`
Frequency uint32 `xml:"frequency"`
TimeStamp uint64 `xml:"timeStamp"`
SeedValue string `xml:"seedValue"`
PreviousOutputValue string `xml:"previousOutputValue"`
SignatureValue string `xml:"signatureValue"`
OutputValue string `xml:"outputValue"`
StatusCode uint32 `xml:"statusCode"`
}
func (d BeaconData) VerificationData() (signed, signature []byte, err error) {
signature, err = hex.DecodeString(d.SignatureValue)
if err != nil {
return nil, nil, err
}
/*
http://hackaday.com/2014/12/19/nist-randomness-beacon/
## Create a bytewise reversed version of the listed signature
## This is necessary b/c Beacon signs with Microsoft CryptoAPI which outputs
## the signature as little-endian instead of big-endian like many other tools
## This may change (personal communication) in a future revision of the Beacon
*/
sigLimit := len(signature) - 1
for i := 0; i <= sigLimit/2; i++ {
signature[i], signature[sigLimit-i] = signature[sigLimit-i], signature[i]
}
b := new(bytes.Buffer)
b.Grow(200)
_, _ = b.WriteString(d.Version)
binary.Write(b, binary.BigEndian, d.Frequency)
binary.Write(b, binary.BigEndian, d.TimeStamp)
seed, err := hex.DecodeString(d.SeedValue)
if err != nil {
return nil, nil, err
}
_, _ = b.Write(seed)
prev, err := hex.DecodeString(d.PreviousOutputValue)
if err != nil {
return nil, nil, err
}
_, _ = b.Write(prev)
binary.Write(b, binary.BigEndian, d.StatusCode)
return b.Bytes(), signature, nil
}
type beaconMaker struct {
Debug bool
DebugFH io.Writer
verifyCert *x509.Certificate
}
func NewBeaconMaker() beaconMaker {
return beaconMaker{
verifyCert: beaconCertificate(),
}
}
func (m beaconMaker) Debugf(format string, a ...interface{}) {
if !m.Debug {
return
}
if m.DebugFH == nil {
// might later capture in a buffer within the maker, to print, but then
// maker is mutable; for now, just require both the flag and the writer
return
}
fmt.Fprintf(m.DebugFH, format, a...)
}
func (m beaconMaker) DebugCertificate() {
m.Debugf("certificate: %#v\n", m.verifyCert)
}
func (m beaconMaker) ValidateSignature(signed, signature []byte) error {
return m.verifyCert.CheckSignature(x509.SHA512WithRSA, signed, signature)
}
func (m beaconMaker) NewByXMLBytes(rawxml []byte) (BeaconData, error) {
var bd BeaconData
err := xml.Unmarshal(rawxml, &bd)
if err != nil {
return BeaconData{}, fmt.Errorf("decoding beacon data from XML failed: %s", err)
}
m.Debugf("raw beacon XML: %#v\n", bd)
verifiable, signature, err := bd.VerificationData()
if err != nil {
return bd, fmt.Errorf("preparing beacon data for signature verification failed: %s", err)
}
if len(verifiable) == 0 {
panic("beacon signature verifiable form of length 0")
}
if len(signature) == 0 {
panic("beacon signature of length 0")
}
if m.Debug {
m.Debugf("verification form: %s\nsignature: %s\n", hex.EncodeToString(verifiable), hex.EncodeToString(signature))
}
err = m.ValidateSignature(verifiable, signature)
if err != nil {
return bd, fmt.Errorf("failed to verify beacon signature: %s", err)
}
m.Debugf("signature verification succeeded\n")
return bd, nil
}
func (m beaconMaker) NewByTimestamp(ts int64) (BeaconData, error) {
if ts%60 != 0 {
m.Debugf("trimming %d seconds from %d\n", ts%60, ts)
ts -= ts % 60
}
m.Debugf("fetching beacon for timestamp: %d\n", ts)
rawBeaconData, err := fetchRecord(ts)
if err != nil {
return BeaconData{}, fmt.Errorf("fetching beacon data failed: %s", err)
}
m.Debugf("fetched %d octets in beacon data\n", len(rawBeaconData))
m.Debugf("RAW: %q\n\n", rawBeaconData)
return m.NewByXMLBytes(rawBeaconData)
}
var beaconCmdlineFlags struct {
debug bool
}
func init() {
flag.BoolVar(&beaconCmdlineFlags.debug, "beacon-debug", false, "debug beacon operation")
}
func main() {
flag.Parse()
maker := NewBeaconMaker()
if beaconCmdlineFlags.debug {
maker.Debug = true
maker.DebugFH = os.Stderr
//maker.DebugCertificate()
}
var err error
timeStamp := time.Now().Unix()
if len(flag.Args()) > 0 {
timeStamp, err = strconv.ParseInt(flag.Arg(0), 0, 64)
if err != nil {
fmt.Fprintf(os.Stderr, "Usage: %s [<timestamp-Unix-epoch-secs>]\n", os.Args[0])
fmt.Fprintln(os.Stderr, " A timestamp of 0 uses a hard-coded XML fragment in the source code")
os.Exit(1)
}
}
beacon, err := maker.NewByTimestamp(timeStamp)
if err != nil {
fmt.Fprintf(os.Stderr, "beacon object creation failed: %s\n", err)
os.Exit(1)
}
seed, err := hex.DecodeString(beacon.SeedValue)
if err != nil {
// should have been caught during verification
panic(err.Error())
}
fmt.Printf("%v\n", seed)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment